@motionscript/browser 0.0.0-stage → 0.1.0-alpha.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 (120) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/README.md +68 -3
  4. package/dist/asset-bytes.d.ts +50 -0
  5. package/dist/asset-bytes.d.ts.map +1 -0
  6. package/dist/asset-bytes.js +60 -0
  7. package/dist/asset-bytes.js.map +1 -0
  8. package/dist/audio/bus-graph.d.ts +43 -0
  9. package/dist/audio/bus-graph.d.ts.map +1 -0
  10. package/dist/audio/bus-graph.js +104 -0
  11. package/dist/audio/bus-graph.js.map +1 -0
  12. package/dist/audio/filter-graph.d.ts +58 -0
  13. package/dist/audio/filter-graph.d.ts.map +1 -0
  14. package/dist/audio/filter-graph.js +235 -0
  15. package/dist/audio/filter-graph.js.map +1 -0
  16. package/dist/audio/index.d.ts +5 -0
  17. package/dist/audio/index.d.ts.map +1 -0
  18. package/dist/audio/index.js +12 -0
  19. package/dist/audio/index.js.map +1 -0
  20. package/dist/audio/mixer.d.ts +59 -0
  21. package/dist/audio/mixer.d.ts.map +1 -0
  22. package/dist/audio/mixer.js +193 -0
  23. package/dist/audio/mixer.js.map +1 -0
  24. package/dist/audio/player.d.ts +49 -0
  25. package/dist/audio/player.d.ts.map +1 -0
  26. package/dist/audio/player.js +226 -0
  27. package/dist/audio/player.js.map +1 -0
  28. package/dist/browser/audio.js +2 -0
  29. package/dist/browser/audio.js.map +7 -0
  30. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +2 -0
  31. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +7 -0
  32. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +2 -0
  33. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js.map +7 -0
  34. package/dist/browser/chunks/chunk-CTLBXZSU.js +4119 -0
  35. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +7 -0
  36. package/dist/browser/chunks/chunk-FQ32PDIU.js +2 -0
  37. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +7 -0
  38. package/dist/browser/chunks/chunk-FY3BTKY4.js +2 -0
  39. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +7 -0
  40. package/dist/browser/chunks/chunk-NSFLRI4P.js +2 -0
  41. package/dist/browser/chunks/chunk-NSFLRI4P.js.map +7 -0
  42. package/dist/browser/chunks/three.module-Z4EOVUWF.js +2 -0
  43. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +7 -0
  44. package/dist/browser/esbuild.wasm +0 -0
  45. package/dist/browser/index.js +2172 -0
  46. package/dist/browser/index.js.map +7 -0
  47. package/dist/browser/manifest.json +15 -0
  48. package/dist/concat.d.ts +95 -0
  49. package/dist/concat.d.ts.map +1 -0
  50. package/dist/concat.js +174 -0
  51. package/dist/concat.js.map +1 -0
  52. package/dist/decode/image-levels.d.ts +137 -0
  53. package/dist/decode/image-levels.d.ts.map +1 -0
  54. package/dist/decode/image-levels.js +477 -0
  55. package/dist/decode/image-levels.js.map +1 -0
  56. package/dist/decode/levels.d.ts +37 -0
  57. package/dist/decode/levels.d.ts.map +1 -0
  58. package/dist/decode/levels.js +68 -0
  59. package/dist/decode/levels.js.map +1 -0
  60. package/dist/decode/video-levels.d.ts +37 -0
  61. package/dist/decode/video-levels.d.ts.map +1 -0
  62. package/dist/decode/video-levels.js +95 -0
  63. package/dist/decode/video-levels.js.map +1 -0
  64. package/dist/dynamic-nodes.d.ts +9 -0
  65. package/dist/dynamic-nodes.d.ts.map +1 -0
  66. package/dist/dynamic-nodes.js +56 -0
  67. package/dist/dynamic-nodes.js.map +1 -0
  68. package/dist/engine.d.ts +59 -0
  69. package/dist/engine.d.ts.map +1 -0
  70. package/dist/engine.js +171 -0
  71. package/dist/engine.js.map +1 -0
  72. package/dist/exporter.d.ts +163 -0
  73. package/dist/exporter.d.ts.map +1 -0
  74. package/dist/exporter.js +263 -0
  75. package/dist/exporter.js.map +1 -0
  76. package/dist/getter.d.ts +10 -0
  77. package/dist/getter.d.ts.map +1 -0
  78. package/dist/getter.js +38 -0
  79. package/dist/getter.js.map +1 -0
  80. package/dist/index.d.ts +22 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +72 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/measure-context.d.ts +12 -0
  85. package/dist/measure-context.d.ts.map +1 -0
  86. package/dist/measure-context.js +12 -0
  87. package/dist/measure-context.js.map +1 -0
  88. package/dist/mix-timeline-audio.d.ts +42 -0
  89. package/dist/mix-timeline-audio.d.ts.map +1 -0
  90. package/dist/mix-timeline-audio.js +63 -0
  91. package/dist/mix-timeline-audio.js.map +1 -0
  92. package/dist/output.d.ts +7 -0
  93. package/dist/output.d.ts.map +1 -0
  94. package/dist/output.js +6 -0
  95. package/dist/output.js.map +1 -0
  96. package/dist/render-context.d.ts +30 -0
  97. package/dist/render-context.d.ts.map +1 -0
  98. package/dist/render-context.js +55 -0
  99. package/dist/render-context.js.map +1 -0
  100. package/dist/screenshot.d.ts +45 -0
  101. package/dist/screenshot.d.ts.map +1 -0
  102. package/dist/screenshot.js +32 -0
  103. package/dist/screenshot.js.map +1 -0
  104. package/dist/still.d.ts +151 -0
  105. package/dist/still.d.ts.map +1 -0
  106. package/dist/still.js +222 -0
  107. package/dist/still.js.map +1 -0
  108. package/dist/storage-adapter.d.ts +367 -0
  109. package/dist/storage-adapter.d.ts.map +1 -0
  110. package/dist/storage-adapter.js +1278 -0
  111. package/dist/storage-adapter.js.map +1 -0
  112. package/dist/three/renderer.d.ts +48 -0
  113. package/dist/three/renderer.d.ts.map +1 -0
  114. package/dist/three/renderer.js +177 -0
  115. package/dist/three/renderer.js.map +1 -0
  116. package/dist/ticker.d.ts +42 -0
  117. package/dist/ticker.d.ts.map +1 -0
  118. package/dist/ticker.js +143 -0
  119. package/dist/ticker.js.map +1 -0
  120. package/package.json +74 -3
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ // @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,
2
+ // running in the browser (canvas mount, video export, audio playback/clock).
3
+ export { WebRenderContext } from "./render-context";
4
+ export { WebStorageAdapter } from "./storage-adapter";
5
+ // The seam letting a host answer for an asset's bytes before the adapter goes
6
+ // to the network — see `asset-bytes.ts` for why it is module-level.
7
+ export { setAssetByteSource } from "./asset-bytes";
8
+ export { getCanvasKit } from "./getter";
9
+ // Re-exported from @motionscript/skia-render, whose canonical home these are —
10
+ // kept on this barrel so the specifier consumers already use keeps resolving.
11
+ export { EffectRegistry } from "@motionscript/skia-render";
12
+ export { exportVideo, } from "./exporter";
13
+ // mediabunny's resolution-aware quality levels, re-exported so a host can pick
14
+ // one for `ExportParams.video.bitrate` without taking a direct dependency on the
15
+ // encoder library this package happens to use.
16
+ export { QUALITY_VERY_LOW, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_HIGH, QUALITY_VERY_HIGH, } from "mediabunny";
17
+ export { exportScreenshot } from "./screenshot";
18
+ // Joins separately-encoded MP4s end to end without re-encoding. What makes it
19
+ // worth rendering an export in parallel at all: the parts still have to become
20
+ // one file, and re-encoding to join them gives back everything the parallelism
21
+ // won. See `concat.ts` for the two things a caller has to guarantee.
22
+ export { concatVideoSegments, ConcatMismatchError } from "./concat";
23
+ // Mixes a timeline's audio without rendering a frame — the other half of a split
24
+ // export, whose video arrives in separately-encoded pieces that AAC cannot be
25
+ // concatenated across. See `mix-timeline-audio.ts`.
26
+ export { mixTimelineAudio } from "./mix-timeline-audio";
27
+ // Single frames as a first-class capability: one long-lived renderer that repaints
28
+ // into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`
29
+ // above is the one-shot wrapper over it.
30
+ export { StillRenderer, createStillRenderer } from "./still";
31
+ export { WebAudioSink as WebAudioPlayer } from "./audio/player";
32
+ // Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the
33
+ // import to prefer — it reaches it without loading CanvasKit, since none of it
34
+ // draws.
35
+ export { mixAudio, encodeWav } from "./audio/mixer";
36
+ export { WebMeasureContext } from "./measure-context";
37
+ export { WebTicker } from "./ticker";
38
+ // Exported from the barrel, never from a subpath of its own: the 3D
39
+ // registrations at the bottom of this file are what make `Canvas3D` draw
40
+ // anything, and a consumer reaching `createBrowserEngine` without evaluating
41
+ // this module would get an engine whose 3D silently renders empty.
42
+ export { createBrowserEngine } from "./engine";
43
+ // ─── 3D ──────────────────────────────────────────────────────────────────────
44
+ // The reconciler, handlers and lazy `three` boundary all live in
45
+ // @motionscript/skia-render now; this package supplies only the WebGL renderer
46
+ // that rasterizes what they build. Re-exported here so the specifiers consumers
47
+ // already use keep resolving.
48
+ export { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from "@motionscript/skia-render";
49
+ import { registerCanvas3DBackend, registerCanvas3DRendererHost } from "@motionscript/skia-render";
50
+ import { webCanvas3DRendererHost } from "./three/renderer";
51
+ // Hand core the three-loading hook so the loader `Canvas3D.declareAssets()` declares
52
+ // can preload the runtime before any frame draws. Done at module scope rather
53
+ // than lazily because core has no way to reach into this package on its own — the
54
+ // registration is the seam.
55
+ //
56
+ // Note this package declares `sideEffects: false`, which is still accurate: the
57
+ // call has no observable effect for a 2D-only project (it just stores a function
58
+ // reference), and because it lives in the barrel every consumer imports, a
59
+ // bundler can't drop it while keeping anything else here.
60
+ registerCanvas3DBackend();
61
+ // Hand skia-render the WebGL renderer, for the same reason and with the same
62
+ // constraint: it MUST be registered from this barrel.
63
+ //
64
+ // `./three/renderer` is no longer statically imported by anything else in this
65
+ // package — the reconciler that used to import it moved out. Combined with
66
+ // `sideEffects: false`, a module-scope registration inside that file could be
67
+ // tree-shaken away, and the failure is silent: `canvas3DBackend()` returns null
68
+ // forever, the warm-and-retry loop exhausts its three passes, and every frame
69
+ // ships its 2D parts with no 3D and no error. Registering here keeps the module
70
+ // reachable and the behaviour explicit.
71
+ registerCanvas3DRendererHost(webCanvas3DRendererHost);
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,8EAA8E;AAC9E,oEAAoE;AACpE,OAAO,EAAE,kBAAkB,EAAwB,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,+EAA+E;AAC/E,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAC;AACpB,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACH,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAuF,MAAM,cAAc,CAAC;AACrI,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACxF,iFAAiF;AACjF,8EAA8E;AAC9E,oDAAoD;AACpD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,sBAAsB,CAAC;AACrF,mFAAmF;AACnF,qFAAqF;AACrF,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,2EAA2E;AAC3E,+EAA+E;AAC/E,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAwB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,oEAAoE;AACpE,yEAAyE;AACzE,6EAA6E;AAC7E,mEAAmE;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG/C,gFAAgF;AAEhF,iEAAiE;AACjE,+EAA+E;AAC/E,gFAAgF;AAChF,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGnH,OAAO,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,qFAAqF;AACrF,8EAA8E;AAC9E,kFAAkF;AAClF,4BAA4B;AAC5B,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,2EAA2E;AAC3E,0DAA0D;AAC1D,uBAAuB,EAAE,CAAC;AAE1B,6EAA6E;AAC7E,sDAAsD;AACtD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,wCAAwC;AACxC,4BAA4B,CAAC,uBAAuB,CAAC,CAAC","sourcesContent":["// @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,\n// running in the browser (canvas mount, video export, audio playback/clock).\n\nexport { WebRenderContext } from \"./render-context\";\nexport { WebStorageAdapter } from \"./storage-adapter\";\n// The seam letting a host answer for an asset's bytes before the adapter goes\n// to the network — see `asset-bytes.ts` for why it is module-level.\nexport { setAssetByteSource, type AssetByteSource } from \"./asset-bytes\";\nexport { getCanvasKit } from \"./getter\";\n// Re-exported from @motionscript/skia-render, whose canonical home these are —\n// kept on this barrel so the specifier consumers already use keeps resolving.\nexport { EffectRegistry } from \"@motionscript/skia-render\";\nexport type { EffectHandler, EffectGeometry, EffectResources, EffectTarget, RenderEffect } from \"@motionscript/skia-render\";\nexport {\n exportVideo,\n type ExportParams,\n type ExportProgressCallback,\n type VideoEncoderOptions,\n type AudioEncoderOptions,\n} from \"./exporter\";\n// mediabunny's resolution-aware quality levels, re-exported so a host can pick\n// one for `ExportParams.video.bitrate` without taking a direct dependency on the\n// encoder library this package happens to use.\nexport {\n QUALITY_VERY_LOW,\n QUALITY_LOW,\n QUALITY_MEDIUM,\n QUALITY_HIGH,\n QUALITY_VERY_HIGH,\n type Quality,\n} from \"mediabunny\";\nexport { exportScreenshot, type ScreenshotParams, type ScreenshotResult, type ScreenshotFormat, type FrameSpec } from \"./screenshot\";\n// Joins separately-encoded MP4s end to end without re-encoding. What makes it\n// worth rendering an export in parallel at all: the parts still have to become\n// one file, and re-encoding to join them gives back everything the parallelism\n// won. See `concat.ts` for the two things a caller has to guarantee.\nexport { concatVideoSegments, ConcatMismatchError, type ConcatOptions } from \"./concat\";\n// Mixes a timeline's audio without rendering a frame — the other half of a split\n// export, whose video arrives in separately-encoded pieces that AAC cannot be\n// concatenated across. See `mix-timeline-audio.ts`.\nexport { mixTimelineAudio, type MixTimelineAudioParams } from \"./mix-timeline-audio\";\n// Single frames as a first-class capability: one long-lived renderer that repaints\n// into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`\n// above is the one-shot wrapper over it.\nexport { StillRenderer, createStillRenderer } from \"./still\";\nexport type {\n StillRendererOptions,\n RenderStillOptions,\n EncodeStillOptions,\n StillSource,\n FrameRef,\n} from \"./still\";\nexport { WebAudioSink as WebAudioPlayer } from \"./audio/player\";\n// Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the\n// import to prefer — it reaches it without loading CanvasKit, since none of it\n// draws.\nexport { mixAudio, encodeWav, type MixAudioOptions } from \"./audio/mixer\";\nexport { WebMeasureContext } from \"./measure-context\";\nexport { WebTicker } from \"./ticker\";\n\n// Exported from the barrel, never from a subpath of its own: the 3D\n// registrations at the bottom of this file are what make `Canvas3D` draw\n// anything, and a consumer reaching `createBrowserEngine` without evaluating\n// this module would get an engine whose 3D silently renders empty.\nexport { createBrowserEngine } from \"./engine\";\nexport type { BrowserEngineOptions, BrowserEncodeOptions } from \"./engine\";\n\n// ─── 3D ──────────────────────────────────────────────────────────────────────\n\n// The reconciler, handlers and lazy `three` boundary all live in\n// @motionscript/skia-render now; this package supplies only the WebGL renderer\n// that rasterizes what they build. Re-exported here so the specifiers consumers\n// already use keep resolving.\nexport { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from \"@motionscript/skia-render\";\nexport type { Canvas3DAssets, RenderedCanvas3D } from \"@motionscript/skia-render\";\n\nimport { registerCanvas3DBackend, registerCanvas3DRendererHost } from \"@motionscript/skia-render\";\nimport { webCanvas3DRendererHost } from \"./three/renderer\";\n\n// Hand core the three-loading hook so the loader `Canvas3D.declareAssets()` declares\n// can preload the runtime before any frame draws. Done at module scope rather\n// than lazily because core has no way to reach into this package on its own — the\n// registration is the seam.\n//\n// Note this package declares `sideEffects: false`, which is still accurate: the\n// call has no observable effect for a 2D-only project (it just stores a function\n// reference), and because it lives in the barrel every consumer imports, a\n// bundler can't drop it while keeping anything else here.\nregisterCanvas3DBackend();\n\n// Hand skia-render the WebGL renderer, for the same reason and with the same\n// constraint: it MUST be registered from this barrel.\n//\n// `./three/renderer` is no longer statically imported by anything else in this\n// package — the reconciler that used to import it moved out. Combined with\n// `sideEffects: false`, a module-scope registration inside that file could be\n// tree-shaken away, and the failure is silent: `canvas3DBackend()` returns null\n// forever, the warm-and-retry loop exhausts its three passes, and every frame\n// ships its 2D parts with no 3D and no error. Registering here keeps the module\n// reachable and the behaviour explicit.\nregisterCanvas3DRendererHost(webCanvas3DRendererHost);\n"]}
@@ -0,0 +1,12 @@
1
+ import { SkiaMeasureContext } from "@motionscript/skia-render/measure-context";
2
+ /**
3
+ * Browser alias for {@link SkiaMeasureContext}.
4
+ *
5
+ * A real subclass rather than a re-export, so `instanceof WebMeasureContext` and the
6
+ * name in a stack trace both still mean something, and so a future
7
+ * browser-specific measurement path has an obvious home. Text measurement itself
8
+ * is pure Skia paragraph layout and needs no platform code at all.
9
+ */
10
+ export declare class WebMeasureContext extends SkiaMeasureContext {
11
+ }
12
+ //# sourceMappingURL=measure-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure-context.d.ts","sourceRoot":"","sources":["../src/measure-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,kBAAkB;CAAI"}
@@ -0,0 +1,12 @@
1
+ import { SkiaMeasureContext } from "@motionscript/skia-render/measure-context";
2
+ /**
3
+ * Browser alias for {@link SkiaMeasureContext}.
4
+ *
5
+ * A real subclass rather than a re-export, so `instanceof WebMeasureContext` and the
6
+ * name in a stack trace both still mean something, and so a future
7
+ * browser-specific measurement path has an obvious home. Text measurement itself
8
+ * is pure Skia paragraph layout and needs no platform code at all.
9
+ */
10
+ export class WebMeasureContext extends SkiaMeasureContext {
11
+ }
12
+ //# sourceMappingURL=measure-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure-context.js","sourceRoot":"","sources":["../src/measure-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,kBAAkB;CAAI","sourcesContent":["import { SkiaMeasureContext } from \"@motionscript/skia-render/measure-context\";\n\n/**\n * Browser alias for {@link SkiaMeasureContext}.\n *\n * A real subclass rather than a re-export, so `instanceof WebMeasureContext` and the\n * name in a stack trace both still mean something, and so a future\n * browser-specific measurement path has an obvious home. Text measurement itself\n * is pure Skia paragraph layout and needs no platform code at all.\n */\nexport class WebMeasureContext extends SkiaMeasureContext { }\n"]}
@@ -0,0 +1,42 @@
1
+ import { Registry, type AssetManifest, type ProjectDocument, type SceneDocument, type Size2D } from "@motionscript/core";
2
+ export interface MixTimelineAudioParams {
3
+ /** A project, or a lone still or composition as the one track of one. */
4
+ document: ProjectDocument | SceneDocument;
5
+ /** The vocabulary its clips compile against. Defaults to what the library ships. */
6
+ registry?: Registry;
7
+ /** Win over the document's own; 1920×1080 at 60 fps when neither says. */
8
+ viewport?: Size2D;
9
+ fps?: number;
10
+ manifest?: AssetManifest;
11
+ /** Mixdown rate. Defaults to the mixer's own (44100). */
12
+ sampleRate?: number;
13
+ wasmUrl?: string;
14
+ }
15
+ /**
16
+ * Mix a timeline's audio without rendering a single frame.
17
+ *
18
+ * `exportVideo` normally does this as part of the render, and for a
19
+ * single-threaded export that is exactly right. A **split** export cannot: its
20
+ * video is rendered in pieces by separate workers, and concatenating
21
+ * separately-encoded AAC puts the encoder's priming delay at every join — a click
22
+ * at each boundary and a track that drifts further out of sync with every one. So
23
+ * the coordinator mixes the whole timeline once, here, and the joined file gets a
24
+ * single continuous audio track.
25
+ *
26
+ * Returns `null` when the timeline schedules no audio at all, which is the common
27
+ * case and not a failure — the caller should then mux video only rather than
28
+ * declare an empty track.
29
+ *
30
+ * ## Why this discovers
31
+ *
32
+ * Audio requests are an *output of discovery*: each node declares its clips as it
33
+ * enters the tree, and the pass that finds what a clip's frames draw is the one
34
+ * that puts its nodes in the tree, so there is no way to know what plays when
35
+ * without it.
36
+ *
37
+ * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render
38
+ * context: discovery lays each clip out, which needs text measured, not
39
+ * rasterized. No canvas, no surface, no GPU.
40
+ */
41
+ export declare function mixTimelineAudio(params: MixTimelineAudioParams): Promise<AudioBuffer | null>;
42
+ //# sourceMappingURL=mix-timeline-audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;AAS5B,MAAM,WAAW,sBAAsB;IACnC,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,aAAa,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,sBAAsB,GAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B7B"}
@@ -0,0 +1,63 @@
1
+ import { ManifestAssetCatalog, AssetTimeline, Registry, resolveProjectDocument, } from "@motionscript/core";
2
+ import { collectAudio } from "@motionscript/skia-render/export";
3
+ import { mixAudio } from "./audio/mixer";
4
+ import { getCanvasKit } from "./getter";
5
+ import { WebMeasureContext } from "./measure-context";
6
+ import { DEFAULT_OUTPUT } from "./output";
7
+ import { WebStorageAdapter } from "./storage-adapter";
8
+ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
9
+ /**
10
+ * Mix a timeline's audio without rendering a single frame.
11
+ *
12
+ * `exportVideo` normally does this as part of the render, and for a
13
+ * single-threaded export that is exactly right. A **split** export cannot: its
14
+ * video is rendered in pieces by separate workers, and concatenating
15
+ * separately-encoded AAC puts the encoder's priming delay at every join — a click
16
+ * at each boundary and a track that drifts further out of sync with every one. So
17
+ * the coordinator mixes the whole timeline once, here, and the joined file gets a
18
+ * single continuous audio track.
19
+ *
20
+ * Returns `null` when the timeline schedules no audio at all, which is the common
21
+ * case and not a failure — the caller should then mux video only rather than
22
+ * declare an empty track.
23
+ *
24
+ * ## Why this discovers
25
+ *
26
+ * Audio requests are an *output of discovery*: each node declares its clips as it
27
+ * enters the tree, and the pass that finds what a clip's frames draw is the one
28
+ * that puts its nodes in the tree, so there is no way to know what plays when
29
+ * without it.
30
+ *
31
+ * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render
32
+ * context: discovery lays each clip out, which needs text measured, not
33
+ * rasterized. No canvas, no surface, no GPU.
34
+ */
35
+ export async function mixTimelineAudio(params) {
36
+ const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate, wasmUrl } = params;
37
+ const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
38
+ if (project.tracks.length === 0)
39
+ return null;
40
+ const viewport = project.viewport;
41
+ const fps = project.fps;
42
+ // CanvasKit only for its font manager — `WebMeasureContext` shapes text through it.
43
+ const canvasKit = await getCanvasKit(wasmUrl);
44
+ const catalog = new ManifestAssetCatalog(manifest);
45
+ const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);
46
+ const measurer = new WebMeasureContext(storage);
47
+ // Audio is declared from each node's arrival state, so no asset windows are refined.
48
+ const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });
49
+ try {
50
+ const precomp = precomper.run();
51
+ const scheduled = collectAudio(precomp);
52
+ if (scheduled.length === 0)
53
+ return null;
54
+ return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });
55
+ }
56
+ finally {
57
+ precomper.dispose();
58
+ // The adapter holds decode sessions and cached images; without this a mix
59
+ // leaks them for the life of the page.
60
+ storage.dispose();
61
+ }
62
+ }
63
+ //# sourceMappingURL=mix-timeline-audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAgBtD,MAAM,cAAc,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,MAA8B;IAE9B,MAAM,EAAE,QAAQ,GAAG,IAAI,QAAQ,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAC7F,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAS,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAI,CAAC;IAEzB,oFAAoF;IACpF,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAEhD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxH,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAExC,OAAO,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;YAAS,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACL,CAAC","sourcesContent":["import {\n ManifestAssetCatalog,\n AssetTimeline,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type ProjectDocument,\n type SceneDocument,\n type Size2D,\n} from \"@motionscript/core\";\nimport { collectAudio } from \"@motionscript/skia-render/export\";\n\nimport { mixAudio } from \"./audio/mixer\";\nimport { getCanvasKit } from \"./getter\";\nimport { WebMeasureContext } from \"./measure-context\";\nimport { DEFAULT_OUTPUT } from \"./output\";\nimport { WebStorageAdapter } from \"./storage-adapter\";\n\nexport interface MixTimelineAudioParams {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | SceneDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n manifest?: AssetManifest;\n /** Mixdown rate. Defaults to the mixer's own (44100). */\n sampleRate?: number;\n wasmUrl?: string;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/**\n * Mix a timeline's audio without rendering a single frame.\n *\n * `exportVideo` normally does this as part of the render, and for a\n * single-threaded export that is exactly right. A **split** export cannot: its\n * video is rendered in pieces by separate workers, and concatenating\n * separately-encoded AAC puts the encoder's priming delay at every join — a click\n * at each boundary and a track that drifts further out of sync with every one. So\n * the coordinator mixes the whole timeline once, here, and the joined file gets a\n * single continuous audio track.\n *\n * Returns `null` when the timeline schedules no audio at all, which is the common\n * case and not a failure — the caller should then mux video only rather than\n * declare an empty track.\n *\n * ## Why this discovers\n *\n * Audio requests are an *output of discovery*: each node declares its clips as it\n * enters the tree, and the pass that finds what a clip's frames draw is the one\n * that puts its nodes in the tree, so there is no way to know what plays when\n * without it.\n *\n * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render\n * context: discovery lays each clip out, which needs text measured, not\n * rasterized. No canvas, no surface, no GPU.\n */\nexport async function mixTimelineAudio(\n params: MixTimelineAudioParams,\n): Promise<AudioBuffer | null> {\n const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate, wasmUrl } = params;\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) return null;\n const viewport = project.viewport!;\n const fps = project.fps!;\n\n // CanvasKit only for its font manager — `WebMeasureContext` shapes text through it.\n const canvasKit = await getCanvasKit(wasmUrl);\n const catalog = new ManifestAssetCatalog(manifest);\n const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);\n const measurer = new WebMeasureContext(storage);\n\n // Audio is declared from each node's arrival state, so no asset windows are refined.\n const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });\n try {\n const precomp = precomper.run();\n\n const scheduled = collectAudio(precomp);\n if (scheduled.length === 0) return null;\n\n return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });\n } finally {\n precomper.dispose();\n // The adapter holds decode sessions and cached images; without this a mix\n // leaks them for the life of the page.\n storage.dispose();\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { Size2D } from "@motionscript/core";
2
+ /** What a document renders at when neither the caller nor the document says. */
3
+ export declare const DEFAULT_OUTPUT: {
4
+ viewport: Size2D;
5
+ fps: number;
6
+ };
7
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,gFAAgF;AAChF,eAAO,MAAM,cAAc,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAG3D,CAAC"}
package/dist/output.js ADDED
@@ -0,0 +1,6 @@
1
+ /** What a document renders at when neither the caller nor the document says. */
2
+ export const DEFAULT_OUTPUT = {
3
+ viewport: { width: 1920, height: 1080 },
4
+ fps: 60,
5
+ };
6
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAsC;IAC7D,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IACvC,GAAG,EAAE,EAAE;CACV,CAAC","sourcesContent":["import type { Size2D } from \"@motionscript/core\";\n\n/** What a document renders at when neither the caller nor the document says. */\nexport const DEFAULT_OUTPUT: { viewport: Size2D; fps: number } = {\n viewport: { width: 1920, height: 1080 },\n fps: 60,\n};\n"]}
@@ -0,0 +1,30 @@
1
+ import type { CanvasKit } from "@motionscript/canvaskit";
2
+ import { SkiaRenderContext } from "@motionscript/skia-render/render-context";
3
+ import type { WebStorageAdapter } from "./storage-adapter";
4
+ /**
5
+ * Browser bindings for {@link SkiaRenderContext}.
6
+ *
7
+ * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,
8
+ * clips, masks, the camera, the 3D composite — lives in
9
+ * `@motionscript/skia-render` and is shared with every other backend. What is
10
+ * genuinely browser-specific is only the two things below: creating a WebGL-backed
11
+ * Skia surface over a `<canvas>`, and encoding a snapshot (this CanvasKit build
12
+ * ships no wasm image encoders, so the encode has to come from the host).
13
+ */
14
+ export declare class WebRenderContext extends SkiaRenderContext {
15
+ constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter);
16
+ /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */
17
+ mount(canvas: HTMLCanvasElement): void;
18
+ /**
19
+ * Core declares `unmount()`; it is `detach()` on the portable base, since
20
+ * "unmount" only means anything where there is a DOM element to unmount from.
21
+ */
22
+ unmount(): void;
23
+ /**
24
+ * Snapshots the current surface and encodes it as an image data URL through a
25
+ * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `"image/jpeg", 0.9`)
26
+ * for other formats.
27
+ */
28
+ screenshot(mime?: string, quality?: number): string | undefined;
29
+ }
30
+ //# sourceMappingURL=render-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,SAAQ,iBAAiB;gBACvC,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB;IAInE,sGAAsG;IACtG,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAMtC;;;OAGG;IACM,OAAO,IAAI,IAAI;IAIxB;;;;OAIG;IACM,UAAU,CAAC,IAAI,GAAE,MAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAkBxF"}
@@ -0,0 +1,55 @@
1
+ import { SkiaRenderContext } from "@motionscript/skia-render/render-context";
2
+ /**
3
+ * Browser bindings for {@link SkiaRenderContext}.
4
+ *
5
+ * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,
6
+ * clips, masks, the camera, the 3D composite — lives in
7
+ * `@motionscript/skia-render` and is shared with every other backend. What is
8
+ * genuinely browser-specific is only the two things below: creating a WebGL-backed
9
+ * Skia surface over a `<canvas>`, and encoding a snapshot (this CanvasKit build
10
+ * ships no wasm image encoders, so the encode has to come from the host).
11
+ */
12
+ export class WebRenderContext extends SkiaRenderContext {
13
+ constructor(canvasKit, storageAdapter) {
14
+ super(canvasKit, storageAdapter);
15
+ }
16
+ /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */
17
+ mount(canvas) {
18
+ const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);
19
+ if (!surface)
20
+ throw new Error("Failed to create CanvasKit surface");
21
+ this.attach(surface);
22
+ }
23
+ /**
24
+ * Core declares `unmount()`; it is `detach()` on the portable base, since
25
+ * "unmount" only means anything where there is a DOM element to unmount from.
26
+ */
27
+ unmount() {
28
+ this.detach();
29
+ }
30
+ /**
31
+ * Snapshots the current surface and encodes it as an image data URL through a
32
+ * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `"image/jpeg", 0.9`)
33
+ * for other formats.
34
+ */
35
+ screenshot(mime = "image/png", quality) {
36
+ const snapshot = this.snapshotPixels();
37
+ if (!snapshot)
38
+ return undefined;
39
+ const { pixels, width, height } = snapshot;
40
+ const canvas = document.createElement("canvas");
41
+ canvas.width = width;
42
+ canvas.height = height;
43
+ const ctx = canvas.getContext("2d");
44
+ if (!ctx)
45
+ return undefined;
46
+ // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,
47
+ // which is exactly ImageData's layout.
48
+ ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);
49
+ // A data: URL rather than a blob: URL — both work as an <img> src and a
50
+ // data URL needs no revoke. `quality` is honored only by lossy formats
51
+ // (e.g. image/jpeg); the browser ignores it for image/png.
52
+ return canvas.toDataURL(mime, quality);
53
+ }
54
+ }
55
+ //# sourceMappingURL=render-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAG7E;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACnD,YAAY,SAAoB,EAAE,cAAiC;QAC/D,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACrC,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAyB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACM,OAAO;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACM,UAAU,CAAC,OAAe,WAAW,EAAE,OAAgB;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,4EAA4E;QAC5E,uCAAuC;QACvC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ","sourcesContent":["import type { CanvasKit } from \"@motionscript/canvaskit\";\nimport { SkiaRenderContext } from \"@motionscript/skia-render/render-context\";\nimport type { WebStorageAdapter } from \"./storage-adapter\";\n\n/**\n * Browser bindings for {@link SkiaRenderContext}.\n *\n * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,\n * clips, masks, the camera, the 3D composite — lives in\n * `@motionscript/skia-render` and is shared with every other backend. What is\n * genuinely browser-specific is only the two things below: creating a WebGL-backed\n * Skia surface over a `<canvas>`, and encoding a snapshot (this CanvasKit build\n * ships no wasm image encoders, so the encode has to come from the host).\n */\nexport class WebRenderContext extends SkiaRenderContext {\n constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter) {\n super(canvasKit, storageAdapter);\n }\n\n /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */\n mount(canvas: HTMLCanvasElement): void {\n const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);\n if (!surface) throw new Error(\"Failed to create CanvasKit surface\");\n this.attach(surface);\n }\n\n /**\n * Core declares `unmount()`; it is `detach()` on the portable base, since\n * \"unmount\" only means anything where there is a DOM element to unmount from.\n */\n override unmount(): void {\n this.detach();\n }\n\n /**\n * Snapshots the current surface and encodes it as an image data URL through a\n * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `\"image/jpeg\", 0.9`)\n * for other formats.\n */\n override screenshot(mime: string = \"image/png\", quality?: number): string | undefined {\n const snapshot = this.snapshotPixels();\n if (!snapshot) return undefined;\n const { pixels, width, height } = snapshot;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return undefined;\n // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,\n // which is exactly ImageData's layout.\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);\n // A data: URL rather than a blob: URL — both work as an <img> src and a\n // data URL needs no revoke. `quality` is honored only by lossy formats\n // (e.g. image/jpeg); the browser ignores it for image/png.\n return canvas.toDataURL(mime, quality);\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { type AssetManifest, type ProjectDocument, type Registry, type SceneDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import type { FrameSpec, ScreenshotFormat } from "@motionscript/skia-render/export";
3
+ import { type FrameRef } from "./still";
4
+ export type { ScreenshotFormat, FrameSpec };
5
+ export type ScreenshotParams = {
6
+ /** A project, or a lone still or composition as the one track of one. */
7
+ document: ProjectDocument | SceneDocument;
8
+ /** The vocabulary its clips compile against. Defaults to what the library ships. */
9
+ registry?: Registry;
10
+ /** Win over the document's own; 1920×1080 at 60 fps when neither says. */
11
+ viewport?: Size2D;
12
+ fps?: number;
13
+ scale?: number;
14
+ manifest?: AssetManifest;
15
+ /** Theme for a document that carries none of its own. */
16
+ theme?: Theme;
17
+ wasmUrl?: string;
18
+ /** Which frame to capture. Defaults to the timeline's first. */
19
+ frame?: FrameRef;
20
+ /** Encoding format (default "png"). */
21
+ format?: ScreenshotFormat;
22
+ /** JPEG quality in [0,1] (ignored for png; default 0.92). */
23
+ quality?: number;
24
+ };
25
+ export type ScreenshotResult = {
26
+ /** The project frame that was captured, after clamping. */
27
+ frame: number;
28
+ totalFrames: number;
29
+ /** Encoded image bytes. */
30
+ bytes: Uint8Array;
31
+ };
32
+ /**
33
+ * Renders a single frame of a document to an image.
34
+ *
35
+ * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.
36
+ * It therefore takes the same path as a live preview, and as the video export at
37
+ * that frame — there is one renderer, not three.
38
+ *
39
+ * For anything that captures **repeatedly** — a thumbnail builder, a preview that
40
+ * repaints as the user edits — hold a {@link StillRenderer} instead. This function
41
+ * builds and tears down a WebGL surface per call, which is the right trade for one
42
+ * capture and the wrong one for a hundred.
43
+ */
44
+ export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
45
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACvG,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpF,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC3B,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,aAAa,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
@@ -0,0 +1,32 @@
1
+ import { resolveProjectDocument, } from "@motionscript/core";
2
+ import { DEFAULT_OUTPUT } from "./output";
3
+ import { createStillRenderer } from "./still";
4
+ /**
5
+ * Renders a single frame of a document to an image.
6
+ *
7
+ * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.
8
+ * It therefore takes the same path as a live preview, and as the video export at
9
+ * that frame — there is one renderer, not three.
10
+ *
11
+ * For anything that captures **repeatedly** — a thumbnail builder, a preview that
12
+ * repaints as the user edits — hold a {@link StillRenderer} instead. This function
13
+ * builds and tears down a WebGL surface per call, which is the right trade for one
14
+ * capture and the wrong one for a hundred.
15
+ */
16
+ export async function exportScreenshot(params) {
17
+ const { registry, scale, manifest, theme, wasmUrl, frame, format = "png", quality = 0.92, } = params;
18
+ const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
19
+ if (project.tracks.length === 0)
20
+ throw new Error("The project has no tracks to screenshot.");
21
+ const renderer = await createStillRenderer({
22
+ viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,
23
+ });
24
+ try {
25
+ const drawn = await renderer.render(project, { frame });
26
+ return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };
27
+ }
28
+ finally {
29
+ renderer.dispose();
30
+ }
31
+ }
32
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,GAEzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAiC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EACzC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;KAC1F,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n resolveProjectDocument,\n type AssetManifest, type ProjectDocument, type Registry, type SceneDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { FrameSpec, ScreenshotFormat } from \"@motionscript/skia-render/export\";\nimport { DEFAULT_OUTPUT } from \"./output\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | SceneDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n wasmUrl?: string;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGL surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, wasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
@@ -0,0 +1,151 @@
1
+ import { Registry, type AssetManifest, type ProjectDocument, type SceneDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import { type DrawnFrame, type FrameSpec, type ScreenshotFormat } from "@motionscript/skia-render/export";
3
+ import { getCanvasKit } from "./getter";
4
+ /** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */
5
+ export type StillSource = ProjectDocument | SceneDocument;
6
+ /**
7
+ * Which frame to draw, in whatever form is most convenient — a global frame
8
+ * index, the timeline's first or last frame, or a `FrameSpec`.
9
+ */
10
+ export type FrameRef = FrameSpec | number | "first" | "last";
11
+ export interface StillRendererOptions {
12
+ /**
13
+ * The canvas to draw into. Omit and the renderer creates its own hidden one
14
+ * and removes it on {@link StillRenderer.dispose} — which is what a one-shot
15
+ * capture wants, while a live preview passes the canvas it already shows.
16
+ */
17
+ canvas?: HTMLCanvasElement;
18
+ /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */
19
+ viewport?: Size2D;
20
+ /** Frame rate for a document that sets none. Defaults to 60. */
21
+ fps?: number;
22
+ /** Device pixel ratio the surface is sized at. Defaults to 1. */
23
+ scale?: number;
24
+ manifest?: AssetManifest;
25
+ /** Theme for a document that carries none of its own. */
26
+ theme?: Theme;
27
+ wasmUrl?: string;
28
+ /**
29
+ * The vocabulary a document is compiled against.
30
+ *
31
+ * Defaults to a fresh one carrying only what the library ships — a renderer
32
+ * owns its registry the way an engine does, so a host with custom node types
33
+ * passes the same one it gave its engine rather than relying on whatever a
34
+ * module happened to register.
35
+ */
36
+ registry?: Registry;
37
+ }
38
+ export interface RenderStillOptions {
39
+ /** Which frame to draw. Defaults to the timeline's first. */
40
+ frame?: FrameRef;
41
+ /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */
42
+ time?: number;
43
+ /**
44
+ * Draw at this pixel ratio instead of the renderer's. **Resizes the canvas and
45
+ * rebuilds the Skia surface**, so keep it off the per-edit path — render the
46
+ * preview at its normal scale and pass this only when capturing at a higher one.
47
+ */
48
+ scale?: number;
49
+ }
50
+ export interface EncodeStillOptions {
51
+ /** Defaults to `"png"`. */
52
+ format?: ScreenshotFormat;
53
+ /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */
54
+ quality?: number;
55
+ }
56
+ /** Normalize the many ways a caller can name a frame into the engine's own form. */
57
+ export declare function toFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
58
+ /**
59
+ * A long-lived single-frame renderer.
60
+ *
61
+ * Where `exportScreenshot` is one frame from a standing start, this keeps the
62
+ * expensive things — the CanvasKit module, the WebGL Skia surface, and the
63
+ * storage adapter's decoded-asset cache — alive across calls, so re-rendering
64
+ * after an edit costs discovering one frame and a draw rather than a new GL
65
+ * context. That is
66
+ * the difference between a one-off capture and a builder that repaints while the
67
+ * user types.
68
+ *
69
+ * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is
70
+ * the pass the video exporter runs per frame. A preview and its exported image
71
+ * cannot drift apart, because there is only one path.
72
+ *
73
+ * ```ts
74
+ * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });
75
+ * await renderer.render({ kind: "still", nodes: [
76
+ * { id: "bg", type: "rect", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: "bg" } },
77
+ * ] });
78
+ * const blob = await renderer.toBlob({ format: "png" });
79
+ * renderer.dispose();
80
+ * ```
81
+ *
82
+ * **The theme is process-global in core.** {@link render} re-applies the
83
+ * document's (or this renderer's) before every draw, so two renderers with
84
+ * different themes still each draw correctly; but anything else that calls
85
+ * `setTheme` between a render and a read of the surface will have been
86
+ * overwritten by the next render.
87
+ */
88
+ export declare class StillRenderer {
89
+ readonly canvas: HTMLCanvasElement;
90
+ private renderContext;
91
+ private storageAdapter;
92
+ private catalog;
93
+ private readonly canvasKit;
94
+ /** True when we made the canvas and must clean it up. */
95
+ private readonly ownsCanvas;
96
+ private viewportValue;
97
+ private readonly fpsValue;
98
+ private scaleValue;
99
+ private manifest;
100
+ private theme?;
101
+ private readonly registry;
102
+ private disposed;
103
+ /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
104
+ constructor(canvasKit: Awaited<ReturnType<typeof getCanvasKit>>, options: StillRendererOptions);
105
+ get viewport(): Size2D;
106
+ get fps(): number;
107
+ get scale(): number;
108
+ /**
109
+ * Draw one frame of `source`, at this renderer's viewport — the surface is
110
+ * the output, so it wins over any size the document asks for.
111
+ */
112
+ render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame>;
113
+ /** Theme for a document that carries none of its own. */
114
+ setTheme(theme?: Theme): void;
115
+ /** Swap the asset manifest, keeping every decode already cached. */
116
+ setManifest(manifest: AssetManifest): void;
117
+ /**
118
+ * Change the output resolution.
119
+ *
120
+ * A full reset, unlike {@link setManifest}: the viewport clamps the resolution
121
+ * images are decoded *at*, so cached decodes made for the old one would be
122
+ * soft at the new one, and the render context's handlers captured the adapter
123
+ * when they were built — so both are rebuilt. Call this when the output format
124
+ * changes, not per edit.
125
+ */
126
+ setViewport(viewport: Size2D): void;
127
+ /** Encode the frame currently on the surface. */
128
+ toBytes(options?: EncodeStillOptions): Promise<Uint8Array>;
129
+ /** Encode the frame currently on the surface. */
130
+ toBlob(options?: EncodeStillOptions): Promise<Blob>;
131
+ /** Encode the frame currently on the surface. */
132
+ toDataURL(options?: EncodeStillOptions): string;
133
+ dispose(): void;
134
+ /**
135
+ * Read the surface back into a 2D canvas, which is what actually encodes: this
136
+ * CanvasKit build ships no wasm image encoders, so the browser's own codecs are
137
+ * the only ones available.
138
+ */
139
+ private snapshotToCanvas;
140
+ private sizeCanvas;
141
+ /** Resize the backing store and build a fresh Skia surface over it. */
142
+ private remount;
143
+ private assertLive;
144
+ }
145
+ /**
146
+ * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
147
+ *
148
+ * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
149
+ */
150
+ export declare function createStillRenderer(options?: StillRendererOptions): Promise<StillRenderer>;
151
+ //# sourceMappingURL=still.d.ts.map