@machinal/scenes 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +201 -0
  3. package/LICENSING.md +13 -0
  4. package/NOTICE +8 -0
  5. package/README.md +152 -2
  6. package/bin/machinal-scenes.mjs +86 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +2 -0
  9. package/dist/runtime/artifact.d.ts +18 -0
  10. package/dist/runtime/artifact.js +26 -0
  11. package/dist/runtime/bridge.d.ts +8 -0
  12. package/dist/runtime/bridge.js +76 -0
  13. package/dist/runtime/choreography.d.ts +32 -0
  14. package/dist/runtime/choreography.js +81 -0
  15. package/dist/runtime/controller.d.ts +5 -0
  16. package/dist/runtime/controller.js +93 -0
  17. package/dist/runtime/geometry.d.ts +20 -0
  18. package/dist/runtime/geometry.js +34 -0
  19. package/dist/runtime/index.d.ts +14 -0
  20. package/dist/runtime/index.js +14 -0
  21. package/dist/runtime/layout.d.ts +17 -0
  22. package/dist/runtime/layout.js +48 -0
  23. package/dist/runtime/math.d.ts +4 -0
  24. package/dist/runtime/math.js +23 -0
  25. package/dist/runtime/media.d.ts +2 -0
  26. package/dist/runtime/media.js +69 -0
  27. package/dist/runtime/models.d.ts +14 -0
  28. package/dist/runtime/models.js +370 -0
  29. package/dist/runtime/objects.d.ts +8 -0
  30. package/dist/runtime/objects.js +24 -0
  31. package/dist/runtime/parameters.d.ts +17 -0
  32. package/dist/runtime/parameters.js +35 -0
  33. package/dist/runtime/resources.d.ts +5 -0
  34. package/dist/runtime/resources.js +63 -0
  35. package/dist/runtime/types.d.ts +76 -0
  36. package/dist/runtime/types.js +1 -0
  37. package/dist/runtime/validation.d.ts +4 -0
  38. package/dist/runtime/validation.js +37 -0
  39. package/dist/runtime/video.d.ts +90 -0
  40. package/dist/runtime/video.js +46 -0
  41. package/dist/svelte/SceneCanvas.svelte +15 -0
  42. package/dist/svelte/SceneCanvas.svelte.d.ts +8 -0
  43. package/dist/svelte/SceneThree.svelte +61 -0
  44. package/dist/svelte/SceneThree.svelte.d.ts +10 -0
  45. package/dist/svelte/SceneVideo.svelte +38 -0
  46. package/dist/svelte/SceneVideo.svelte.d.ts +18 -0
  47. package/dist/svelte/context.d.ts +12 -0
  48. package/dist/svelte/context.js +8 -0
  49. package/dist/svelte/index.d.ts +9 -0
  50. package/dist/svelte/index.js +8 -0
  51. package/dist/svelte/model-assets.d.ts +4 -0
  52. package/dist/svelte/model-assets.js +42 -0
  53. package/dist/svelte/mount.svelte.d.ts +3 -0
  54. package/dist/svelte/mount.svelte.js +42 -0
  55. package/dist/svelte/three-rigs.d.ts +20 -0
  56. package/dist/svelte/three-rigs.js +22 -0
  57. package/dist/svelte/three-session.d.ts +6 -0
  58. package/dist/svelte/three-session.js +66 -0
  59. package/dist/svelte/three.d.ts +15 -0
  60. package/dist/svelte/three.js +33 -0
  61. package/examples/quickstart/index.html +20 -0
  62. package/examples/quickstart/package.json +26 -0
  63. package/examples/quickstart/scene.config.json +11 -0
  64. package/examples/quickstart/scene.html +13 -0
  65. package/examples/quickstart/src/Title.svelte +22 -0
  66. package/examples/quickstart/src/main.ts +86 -0
  67. package/examples/quickstart/src/request.ts +5 -0
  68. package/examples/quickstart/src/scene.ts +12 -0
  69. package/examples/quickstart/svelte.config.js +1 -0
  70. package/examples/quickstart/tsconfig.json +15 -0
  71. package/examples/quickstart/vite.config.js +15 -0
  72. package/native/THIRD_PARTY_NOTICES.txt +158 -0
  73. package/native/darwin-arm64/scene-render +0 -0
  74. package/native/darwin-x64/scene-render +0 -0
  75. package/native/linux-x64/scene-render +0 -0
  76. package/native/manifest.json +33 -0
  77. package/native/win32-x64/scene-render.exe +0 -0
  78. package/node/download.mjs +37 -0
  79. package/node/ffmpeg-releases.mjs +24 -0
  80. package/node/index.d.ts +50 -0
  81. package/node/index.mjs +2 -0
  82. package/node/platforms.mjs +14 -0
  83. package/node/render.mjs +59 -0
  84. package/node/toolchain.mjs +113 -0
  85. package/package.json +89 -4
  86. package/renderer/browser.go +116 -0
  87. package/renderer/browser_log.go +36 -0
  88. package/renderer/browser_log_test.go +48 -0
  89. package/renderer/browser_test.go +307 -0
  90. package/renderer/cmd/scene-render/main.go +118 -0
  91. package/renderer/cmd/scene-render/server.go +42 -0
  92. package/renderer/encoder.go +116 -0
  93. package/renderer/encoder_test.go +22 -0
  94. package/renderer/go.mod +11 -0
  95. package/renderer/go.sum +10 -0
  96. package/renderer/network.go +50 -0
  97. package/renderer/output.go +93 -0
  98. package/renderer/pipeline.go +108 -0
  99. package/renderer/pipeline_test.go +108 -0
  100. package/renderer/render.go +141 -0
  101. package/renderer/request.go +140 -0
  102. package/renderer/request_test.go +94 -0
  103. package/renderer/types.go +113 -0
  104. package/renderer/worker.go +110 -0
@@ -0,0 +1,113 @@
1
+ package sceneengine
2
+
3
+ import (
4
+ "encoding/json"
5
+ "time"
6
+ )
7
+
8
+ const ProtocolVersion = 1
9
+
10
+ // Config uses frame indexes for duration and seconds only for FPS conversion.
11
+ type Config struct {
12
+ Width int `json:"width" yaml:"width"`
13
+ Height int `json:"height" yaml:"height"`
14
+ FPS float64 `json:"fps" yaml:"fps"`
15
+ DurationInFrames int `json:"durationInFrames" yaml:"durationInFrames"`
16
+ Transparent bool `json:"transparent,omitempty" yaml:"transparent,omitempty"`
17
+ CaptionSafeBottom float64 `json:"captionSafeBottom,omitempty" yaml:"captionSafeBottom,omitempty"`
18
+ }
19
+
20
+ func (c Config) Duration() float64 { return float64(c.DurationInFrames) / c.FPS }
21
+
22
+ // AudioTrack describes a local source; the scene's browser never plays this audio.
23
+ type AudioTrack struct {
24
+ File string `json:"file" yaml:"file"`
25
+ Start float64 `json:"start" yaml:"start"`
26
+ From float64 `json:"from,omitempty" yaml:"from,omitempty"`
27
+ Duration float64 `json:"duration" yaml:"duration"`
28
+ Volume *float64 `json:"volume,omitempty" yaml:"volume,omitempty"`
29
+ }
30
+
31
+ // Request is a portable JSON/YAML render job. Relative audio paths are resolved by the CLI.
32
+ type Request struct {
33
+ Version int `json:"version" yaml:"version"`
34
+ URL string `json:"url" yaml:"url"`
35
+ SceneID string `json:"sceneId" yaml:"sceneId"`
36
+ Seed string `json:"seed,omitempty" yaml:"seed,omitempty"`
37
+ Config Config `json:"config" yaml:"config"`
38
+ Props map[string]any `json:"props,omitempty" yaml:"props,omitempty"`
39
+ Audio []AudioTrack `json:"audio,omitempty" yaml:"audio,omitempty"`
40
+ }
41
+
42
+ // Manifest records the resolved scene parameters reported by the browser runtime.
43
+ type Manifest struct {
44
+ Version int `json:"version"`
45
+ SceneID string `json:"sceneId"`
46
+ Seed string `json:"seed"`
47
+ Config Config `json:"config"`
48
+ Props map[string]any `json:"props"`
49
+ }
50
+
51
+ type Codec string
52
+
53
+ const (
54
+ H264 Codec = "h264"
55
+ VP9 Codec = "vp9"
56
+ )
57
+
58
+ // Result is the asset boundary between this module and an application.
59
+ type Result struct {
60
+ Version int `json:"version"`
61
+ File string `json:"file"`
62
+ SHA256 string `json:"sha256"`
63
+ Size int64 `json:"size"`
64
+ Duration float64 `json:"duration"`
65
+ Width int `json:"width"`
66
+ Height int `json:"height"`
67
+ FPS float64 `json:"fps"`
68
+ Frames int `json:"frames"`
69
+ HasAudio bool `json:"hasAudio"`
70
+ Transparent bool `json:"transparent"`
71
+ Codec Codec `json:"codec"`
72
+ Scene Manifest `json:"scene"`
73
+ Elapsed time.Duration `json:"elapsedNanoseconds"`
74
+ Metrics RenderMetrics `json:"metrics"`
75
+ }
76
+
77
+ type RenderMetrics struct {
78
+ Workers int `json:"workers"`
79
+ Load time.Duration `json:"loadNanoseconds"`
80
+ CaptureAndEncode time.Duration `json:"captureAndEncodeNanoseconds"`
81
+ EncoderDrain time.Duration `json:"encoderDrainNanoseconds"`
82
+ SeekWorkerTime time.Duration `json:"seekWorkerNanoseconds"`
83
+ CaptureWorkerTime time.Duration `json:"captureWorkerNanoseconds"`
84
+ }
85
+
86
+ type Progress struct {
87
+ WrittenFrames int `json:"writtenFrames"`
88
+ TotalFrames int `json:"totalFrames"`
89
+ Elapsed time.Duration `json:"elapsedNanoseconds"`
90
+ }
91
+
92
+ // Options configures browser ownership and the bounded frame pipeline.
93
+ type Options struct {
94
+ BrowserExecutable string
95
+ FFmpegExecutable string
96
+ Concurrency int
97
+ FrameTimeout time.Duration
98
+ AllowedOrigins []string
99
+ NoSandbox bool
100
+ EnableGPU bool
101
+ Lossless bool
102
+ X264Preset string
103
+ EncoderThreads int
104
+ Progress func(Progress)
105
+ }
106
+
107
+ func (r Request) browserPayload() ([]byte, error) {
108
+ props := r.Props
109
+ if props == nil {
110
+ props = map[string]any{}
111
+ }
112
+ return json.Marshal(Manifest{Version: r.Version, SceneID: r.SceneID, Seed: r.Seed, Config: r.Config, Props: props})
113
+ }
@@ -0,0 +1,110 @@
1
+ package sceneengine
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "time"
7
+
8
+ "github.com/chromedp/cdproto/cdp"
9
+ "github.com/chromedp/cdproto/emulation"
10
+ "github.com/chromedp/cdproto/page"
11
+ "github.com/chromedp/chromedp"
12
+ )
13
+
14
+ type frameWorker struct {
15
+ ctx context.Context
16
+ close func()
17
+ request Request
18
+ manifest Manifest
19
+ options Options
20
+ cancel context.CancelFunc
21
+ seekTime time.Duration
22
+ captureTime time.Duration
23
+ }
24
+
25
+ func (r *Renderer) newWorker(job context.Context, request Request) (*frameWorker, error) {
26
+ tab, cancel := chromedp.NewContext(r.browser)
27
+ stop := context.AfterFunc(job, cancel)
28
+ worker := &frameWorker{ctx: tab, close: func() { stop(); cancel() }, cancel: cancel, request: request, options: r.options}
29
+ // The first action owns the target's event loop, so it must use the tab's
30
+ // lifetime context rather than the short-lived page preparation context.
31
+ timeout := time.AfterFunc(r.options.FrameTimeout, cancel)
32
+ err := chromedp.Do(tab)
33
+ timeout.Stop()
34
+ if err != nil {
35
+ worker.close()
36
+ return nil, fmt.Errorf("create worker tab: %w", err)
37
+ }
38
+ if err := worker.load(); err != nil {
39
+ worker.close()
40
+ return nil, err
41
+ }
42
+ return worker, nil
43
+ }
44
+
45
+ func (w *frameWorker) load() error {
46
+ payload, err := w.request.browserPayload()
47
+ if err != nil {
48
+ return fmt.Errorf("serialize browser request: %w", err)
49
+ }
50
+ ctx, cancel := context.WithTimeout(w.ctx, w.options.FrameTimeout)
51
+ defer cancel()
52
+ err = chromedp.Do(ctx,
53
+ chromedp.EmulateViewport(int64(w.request.Config.Width), int64(w.request.Config.Height)),
54
+ chromedp.Func(func(ctx context.Context, target *chromedp.Target) error {
55
+ if !w.request.Config.Transparent {
56
+ return nil
57
+ }
58
+ // Explicit zero alpha is omitted by the generated protocol type.
59
+ return target.Call(ctx, emulation.SetDefaultBackgroundColorOverride.Method, map[string]any{"color": map[string]any{"r": 0, "g": 0, "b": 0, "a": 0}}, nil)
60
+ }),
61
+ restrictNetwork(w.ctx, w.request.URL, w.options.AllowedOrigins, w.cancel),
62
+ chromedp.Func(func(ctx context.Context, target *chromedp.Target) error {
63
+ _, err := cdp.Call(ctx, target, page.AddScriptToEvaluateOnNewDocument, page.AddScriptToEvaluateOnNewDocumentParams{Source: "window.__sceneRequest = " + string(payload) + ";"})
64
+ return err
65
+ }),
66
+ chromedp.Navigate(w.request.URL),
67
+ chromedp.Poll[chromedp.Void]("Boolean(window.__scene || window.__sceneError)", chromedp.WithPollingTimeout(w.options.FrameTimeout)),
68
+ )
69
+ if err != nil {
70
+ return fmt.Errorf("prepare scene page: %w", err)
71
+ }
72
+ w.manifest, err = chromedp.Run(ctx, chromedp.Evaluate[Manifest](`(async () => {
73
+ if (window.__sceneError) throw new Error(window.__sceneError);
74
+ if (window.__scene.version !== 1) throw new Error('Unsupported scene protocol');
75
+ await window.__scene.ready();
76
+ return window.__scene.manifest;
77
+ })()`, chromedp.EvalAwaitPromise))
78
+ if err != nil {
79
+ return fmt.Errorf("load scene %q: %w", w.request.SceneID, err)
80
+ }
81
+ if w.manifest.Version != ProtocolVersion || w.manifest.SceneID != w.request.SceneID || w.manifest.Config != w.request.Config {
82
+ return fmt.Errorf("scene manifest does not match the render request")
83
+ }
84
+ return nil
85
+ }
86
+
87
+ func (w *frameWorker) capture(job context.Context, frame int) ([]byte, error) {
88
+ ctx, cancel := context.WithTimeout(w.ctx, w.options.FrameTimeout)
89
+ defer cancel()
90
+ stop := context.AfterFunc(job, cancel)
91
+ defer stop()
92
+ started := time.Now()
93
+ err := chromedp.Do(ctx, chromedp.Evaluate[chromedp.Void](fmt.Sprintf("window.__scene.seek(%d)", frame), chromedp.EvalAwaitPromise))
94
+ w.seekTime += time.Since(started)
95
+ if err != nil {
96
+ return nil, fmt.Errorf("seek frame %d: %w", frame, err)
97
+ }
98
+ started = time.Now()
99
+ buffer, err := chromedp.Run(ctx, func(ctx context.Context, target *chromedp.Target) ([]byte, error) {
100
+ beyondViewport, optimize := false, true
101
+ params := page.CaptureScreenshotParams{Format: page.CaptureScreenshotFormatPng, CaptureBeyondViewport: &beyondViewport, OptimizeForSpeed: &optimize}
102
+ result, err := cdp.Call(ctx, target, page.CaptureScreenshot, params)
103
+ return result.Data, err
104
+ })
105
+ w.captureTime += time.Since(started)
106
+ if err != nil {
107
+ return nil, fmt.Errorf("capture frame %d: %w", frame, err)
108
+ }
109
+ return buffer, nil
110
+ }