@motionscript/browser 0.1.0 → 0.1.1-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 (102) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/asset-bytes.d.ts +12 -50
  3. package/dist/asset-bytes.d.ts.map +1 -1
  4. package/dist/asset-bytes.js +20 -52
  5. package/dist/asset-bytes.js.map +1 -1
  6. package/dist/audio/bus-graph.d.ts +4 -39
  7. package/dist/audio/bus-graph.d.ts.map +1 -1
  8. package/dist/audio/bus-graph.js +4 -38
  9. package/dist/audio/bus-graph.js.map +1 -1
  10. package/dist/audio/effect-graph.d.ts +4 -29
  11. package/dist/audio/effect-graph.d.ts.map +1 -1
  12. package/dist/audio/effect-graph.js +4 -31
  13. package/dist/audio/effect-graph.js.map +1 -1
  14. package/dist/audio/loop-region.d.ts +3 -21
  15. package/dist/audio/loop-region.d.ts.map +1 -1
  16. package/dist/audio/loop-region.js +3 -21
  17. package/dist/audio/loop-region.js.map +1 -1
  18. package/dist/audio/mixer.d.ts +9 -41
  19. package/dist/audio/mixer.d.ts.map +1 -1
  20. package/dist/audio/mixer.js +11 -44
  21. package/dist/audio/mixer.js.map +1 -1
  22. package/dist/audio/player.d.ts +3 -17
  23. package/dist/audio/player.d.ts.map +1 -1
  24. package/dist/audio/player.js +4 -23
  25. package/dist/audio/player.js.map +1 -1
  26. package/dist/browser/audio.js +1 -1
  27. package/dist/browser/chunks/chunk-NDJYMKIV.js +2 -0
  28. package/dist/browser/chunks/chunk-NDJYMKIV.js.map +7 -0
  29. package/dist/browser/index.js +19 -19
  30. package/dist/browser/index.js.map +4 -4
  31. package/dist/browser/manifest.json +1 -1
  32. package/dist/browser/ms_renderer_bg.wasm +0 -0
  33. package/dist/concat.d.ts +25 -84
  34. package/dist/concat.d.ts.map +1 -1
  35. package/dist/concat.js +19 -69
  36. package/dist/concat.js.map +1 -1
  37. package/dist/dynamic-nodes.d.ts +1 -6
  38. package/dist/dynamic-nodes.d.ts.map +1 -1
  39. package/dist/dynamic-nodes.js +3 -24
  40. package/dist/dynamic-nodes.js.map +1 -1
  41. package/dist/engine.d.ts +9 -42
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +10 -44
  44. package/dist/engine.js.map +1 -1
  45. package/dist/export-audio.d.ts +1 -6
  46. package/dist/export-audio.d.ts.map +1 -1
  47. package/dist/export-audio.js +1 -6
  48. package/dist/export-audio.js.map +1 -1
  49. package/dist/exporter.d.ts +28 -141
  50. package/dist/exporter.d.ts.map +1 -1
  51. package/dist/exporter.js +15 -90
  52. package/dist/exporter.js.map +1 -1
  53. package/dist/frame-loop.d.ts +19 -105
  54. package/dist/frame-loop.d.ts.map +1 -1
  55. package/dist/frame-loop.js +20 -82
  56. package/dist/frame-loop.js.map +1 -1
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +7 -15
  60. package/dist/index.js.map +1 -1
  61. package/dist/mix-timeline-audio.d.ts +7 -24
  62. package/dist/mix-timeline-audio.d.ts.map +1 -1
  63. package/dist/mix-timeline-audio.js +7 -23
  64. package/dist/mix-timeline-audio.js.map +1 -1
  65. package/dist/native-export.d.ts +29 -0
  66. package/dist/native-export.d.ts.map +1 -0
  67. package/dist/native-export.js +99 -0
  68. package/dist/native-export.js.map +1 -0
  69. package/dist/parallel/export-parallel.d.ts +2 -16
  70. package/dist/parallel/export-parallel.d.ts.map +1 -1
  71. package/dist/parallel/export-parallel.js +1 -11
  72. package/dist/parallel/export-parallel.js.map +1 -1
  73. package/dist/parallel/governor.d.ts +2 -15
  74. package/dist/parallel/governor.d.ts.map +1 -1
  75. package/dist/parallel/governor.js +1 -5
  76. package/dist/parallel/governor.js.map +1 -1
  77. package/dist/parallel/lane.d.ts +1 -5
  78. package/dist/parallel/lane.d.ts.map +1 -1
  79. package/dist/parallel/lane.js.map +1 -1
  80. package/dist/parallel/plan.d.ts +3 -7
  81. package/dist/parallel/plan.d.ts.map +1 -1
  82. package/dist/parallel/plan.js +1 -6
  83. package/dist/parallel/plan.js.map +1 -1
  84. package/dist/parallel/run-lanes.d.ts +4 -18
  85. package/dist/parallel/run-lanes.d.ts.map +1 -1
  86. package/dist/parallel/run-lanes.js +1 -12
  87. package/dist/parallel/run-lanes.js.map +1 -1
  88. package/dist/screenshot.d.ts +1 -12
  89. package/dist/screenshot.d.ts.map +1 -1
  90. package/dist/screenshot.js +1 -12
  91. package/dist/screenshot.js.map +1 -1
  92. package/dist/still.d.ts +17 -74
  93. package/dist/still.d.ts.map +1 -1
  94. package/dist/still.js +13 -50
  95. package/dist/still.js.map +1 -1
  96. package/dist/ticker.d.ts +10 -39
  97. package/dist/ticker.d.ts.map +1 -1
  98. package/dist/ticker.js +13 -45
  99. package/dist/ticker.js.map +1 -1
  100. package/package.json +4 -4
  101. package/dist/browser/chunks/chunk-F3AZKPHU.js +0 -2
  102. package/dist/browser/chunks/chunk-F3AZKPHU.js.map +0 -7
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionscript/browser",
3
- "version": "0.1.0-alpha.10",
3
+ "version": "0.1.0",
4
4
  "exports": {
5
5
  ".": "dist/browser/index.js",
6
6
  "./audio": "dist/browser/audio.js"
Binary file
package/dist/concat.d.ts CHANGED
@@ -1,79 +1,35 @@
1
1
  /**
2
- * Join encoded MP4 or WebM segments end to end, without re-encoding.
3
- *
4
- * The point of this is what it *doesn't* do: no decode, no encode, no pixels.
5
- * Each segment is demuxed to its encoded packets, the packets are re-stamped onto
6
- * one continuous timeline, and a single muxer writes them into one container. The
7
- * bytes of every frame survive untouched, so the result is bit-identical to the
8
- * segments and the whole thing runs at I/O speed rather than encode speed.
9
- *
10
- * It exists so an export can be **split across workers**. Rendering a timeline is
11
- * embarrassingly parallel per scene — each scene's generator is independent — but
12
- * the output has to be one file, and re-encoding the parts to join them would give
13
- * back everything the parallelism won (and lose a generation of quality). Joining
14
- * at the packet level is what makes splitting worth doing.
15
- *
16
- * ## What the caller has to guarantee
17
- *
18
- * **Every segment must be encoded with an identical configuration** — same
19
- * container, codec, resolution and bitrate settings. A container carries exactly one decoder
20
- * configuration for a track, so a segment whose parameter sets differ from the
21
- * first would be described by a configuration that isn't its own, and decode as
22
- * garbage from that point on. That is not a theoretical concern: hardware encoders
23
- * are free to pick different parameter sets for the same nominal settings. This is
24
- * checked rather than assumed — see {@link ConcatMismatchError} — because the
25
- * failure is silent corruption rather than an exception.
26
- *
27
- * **Every segment must begin with a key frame.** Otherwise its first packets refer
28
- * back to frames that lived in the previous segment's GOP and no longer precede
29
- * them. Encoders start with a key frame by default; a caller choosing an explicit
30
- * `keyFrameInterval` needs it to divide the segment length.
31
- *
32
- * Audio is *muxed* here but never *joined*: the segments' own audio is ignored
33
- * entirely, and a single already-mixed buffer is encoded once across the whole
34
- * result (see {@link ConcatOptions.audio}). Concatenating separately-encoded AAC
35
- * would put the encoder's priming delay at every join — a click at each boundary
36
- * and a track that drifts further out of sync with every one — so the timeline's
37
- * audio is mixed in one pass by whoever owns the timeline, and arrives here as
38
- * one buffer.
2
+ * Joins encoded MP4 or WebM segments end to end without re-encoding: segments are demuxed to packets, re-stamped onto
3
+ * one timeline and muxed into one container, so frame bytes survive untouched and it runs at I/O speed. It exists so an
4
+ * export can be **split across workers** without re-encoding the parts (which would lose a generation of quality).
5
+ *
6
+ * The caller must guarantee: **every segment is encoded with an identical configuration** (container, codec,
7
+ * resolution, bitrate settings), since a container carries one decoder configuration per track and a differing one
8
+ * decodes as garbage from that point (hardware encoders may pick different parameter sets for the same settings; this
9
+ * is checked, see {@link ConcatMismatchError}, because the failure is silent). And **every segment begins with a key
10
+ * frame**; a caller choosing an explicit `keyFrameInterval` needs it to divide the segment length.
11
+ *
12
+ * Audio is *muxed* here but never *joined*: the segments' own audio is ignored and one already-mixed buffer is encoded
13
+ * across the whole result (see {@link ConcatOptions.audio}), because separately-encoded AAC puts the encoder's priming
14
+ * delay at every join.
39
15
  */
40
16
  /** Thrown when segments can't be joined losslessly. Carries which segment disagreed. */
41
17
  export declare class ConcatMismatchError extends Error {
42
- /** Index of the offending segment in the input array. */
43
18
  readonly segment: number;
44
- constructor(
45
- /** Index of the offending segment in the input array. */
46
- segment: number, message: string);
19
+ constructor(segment: number, message: string);
47
20
  }
48
21
  export interface ConcatOptions {
49
- /**
50
- * One continuous audio track for the whole joined timeline.
51
- *
52
- * Mixed once by the caller (see `mixTimelineAudio`) rather than per segment,
53
- * and that is not a convenience: separately-encoded AAC carries an encoder
54
- * priming delay at the head of each piece, so joining them puts a click at
55
- * every scene boundary and drifts the track further out of sync with each
56
- * one. One buffer in, one encode, no seams.
57
- */
22
+ /** One continuous audio track, mixed once by the caller (see `mixTimelineAudio`): separately-encoded AAC carries a priming delay per piece, so joining them clicks at every boundary and drifts out of sync. */
58
23
  audio?: AudioBuffer | null;
59
24
  /** Bitrate for that track: AAC in an MP4, Opus in a WebM. Defaults to 192 kbps. */
60
25
  audioBitrate?: number;
61
26
  /**
62
- * Write the result out as it is muxed instead of returning it.
63
- *
64
- * With this the joined file never exists in memory as one buffer, which is
65
- * the difference between an export the process can hold and one it can't:
66
- * a 4× 60fps render is hundreds of megabytes, and the default path costs
67
- * roughly twice that at the tail once the `Blob` copy is counted.
27
+ * Write the result out as it is muxed instead of returning it, so the joined file never exists in memory as one
28
+ * buffer (a 4x 60fps render is hundreds of megabytes, twice that at the tail once the `Blob` copy is counted).
68
29
  *
69
- * **Chunks carry a byte `position` and do not arrive in file order** — a
70
- * muxer seeks back to patch atom sizes. A sink that appends will produce a
71
- * file of exactly the right length whose size fields belong to a different
72
- * one: unplayable, and indistinguishable from success until it is opened.
73
- * Honour `position`.
74
- *
75
- * `concatVideoSegments` resolves to an empty array when this is used; the
76
- * bytes went to the sink.
30
+ * **Chunks carry a byte `position` and do not arrive in file order** (a muxer seeks back to patch atom sizes). A sink
31
+ * that appends produces a file of the right length that is unplayable, indistinguishable from success until opened.
32
+ * Honour `position`. `concatVideoSegments` resolves to an empty array when this is used.
77
33
  */
78
34
  stream?: WritableStream<{
79
35
  type: "write";
@@ -84,8 +40,7 @@ export interface ConcatOptions {
84
40
  /** Joins segments one at a time, in timeline order, as each becomes available. */
85
41
  export interface SegmentJoiner {
86
42
  /**
87
- * Mux the next segment after the ones already appended. Appends must not
88
- * overlap: await each before starting the next.
43
+ * Appends must not overlap: await each before starting the next.
89
44
  *
90
45
  * @throws {ConcatMismatchError} as {@link concatVideoSegments} does.
91
46
  */
@@ -95,27 +50,13 @@ export interface SegmentJoiner {
95
50
  /** Abandon the join; nothing more is written. */
96
51
  cancel(): Promise<void>;
97
52
  }
98
- /**
99
- * {@link concatVideoSegments} one segment at a time, so a split export can write
100
- * each piece the moment it and every piece before it exist, instead of holding
101
- * every segment until the last one lands.
102
- *
103
- * The audio, when given, is encoded as soon as the first segment fixes the
104
- * container, ahead of the video: the muxer holds the (small) audio packets and
105
- * interleaves them as the video catches up.
106
- */
53
+ /** {@link concatVideoSegments} one segment at a time, so a split export writes each piece as soon as it and every piece before it exist. Audio is encoded as soon as the first segment fixes the container, ahead of the video; the muxer interleaves it as the video catches up. */
107
54
  export declare function createSegmentJoiner(options?: ConcatOptions): SegmentJoiner;
108
55
  /**
109
- * Concatenate `segments` (each a complete MP4, or each a complete WebM) into one
110
- * file of the same container.
111
- *
112
- * Segments are joined in array order. Returns the muxed bytes.
56
+ * Concatenates `segments` (all MP4 or all WebM) in array order into one file of the same container.
113
57
  *
114
- * @throws {ConcatMismatchError} if a segment carries no video track, is in another
115
- * container than the first, or has a decoder configuration that differs
116
- * from the first segment's. Callers that can
117
- * fall back to a single-threaded export should catch this and do so — the
118
- * alternative is shipping a file that decodes into garbage halfway through.
58
+ * @throws {ConcatMismatchError} if a segment has no video track, is in another container than the first, or has a
59
+ * differing decoder configuration. Callers that can fall back to a single-threaded export should catch this.
119
60
  */
120
61
  export declare function concatVideoSegments(segments: Uint8Array[], options?: ConcatOptions): Promise<Uint8Array>;
121
62
  //# sourceMappingURL=concat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"concat.d.ts","sourceRoot":"","sources":["../src/concat.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,wFAAwF;AACxF,qBAAa,mBAAoB,SAAQ,KAAK;IAEtC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM;;IADxB,yDAAyD;IAChD,OAAO,EAAE,MAAM,EACxB,OAAO,EAAE,MAAM;CAKtB;AA2CD,MAAM,WAAW,aAAa;IAC1B;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClF;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,qFAAqF;IACrF,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9B,iDAAiD;IACjD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAoE9E;AAuBD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACrC,QAAQ,EAAE,UAAU,EAAE,EACtB,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAcrB"}
1
+ {"version":3,"file":"concat.d.ts","sourceRoot":"","sources":["../src/concat.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;GAcG;AAEH,wFAAwF;AACxF,qBAAa,mBAAoB,SAAQ,KAAK;IAEtC,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM,EACxB,OAAO,EAAE,MAAM;CAKtB;AAoCD,MAAM,WAAW,aAAa;IAC1B,gNAAgN;IAChN,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClF;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC1B;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,qFAAqF;IACrF,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9B,iDAAiD;IACjD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,qRAAqR;AACrR,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAgE9E;AAuBD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACrC,QAAQ,EAAE,UAAU,EAAE,EACtB,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAcrB"}
package/dist/concat.js CHANGED
@@ -1,62 +1,30 @@
1
1
  import { BufferSource, BufferTarget, EncodedPacketSink, EncodedVideoPacketSource, Input, MP4, Output, StreamTarget, WEBM, } from "mediabunny";
2
2
  import { audioTrackSource, formatSpec } from "./formats";
3
3
  /**
4
- * Join encoded MP4 or WebM segments end to end, without re-encoding.
4
+ * Joins encoded MP4 or WebM segments end to end without re-encoding: segments are demuxed to packets, re-stamped onto
5
+ * one timeline and muxed into one container, so frame bytes survive untouched and it runs at I/O speed. It exists so an
6
+ * export can be **split across workers** without re-encoding the parts (which would lose a generation of quality).
5
7
  *
6
- * The point of this is what it *doesn't* do: no decode, no encode, no pixels.
7
- * Each segment is demuxed to its encoded packets, the packets are re-stamped onto
8
- * one continuous timeline, and a single muxer writes them into one container. The
9
- * bytes of every frame survive untouched, so the result is bit-identical to the
10
- * segments and the whole thing runs at I/O speed rather than encode speed.
8
+ * The caller must guarantee: **every segment is encoded with an identical configuration** (container, codec,
9
+ * resolution, bitrate settings), since a container carries one decoder configuration per track and a differing one
10
+ * decodes as garbage from that point (hardware encoders may pick different parameter sets for the same settings; this
11
+ * is checked, see {@link ConcatMismatchError}, because the failure is silent). And **every segment begins with a key
12
+ * frame**; a caller choosing an explicit `keyFrameInterval` needs it to divide the segment length.
11
13
  *
12
- * It exists so an export can be **split across workers**. Rendering a timeline is
13
- * embarrassingly parallel per scene — each scene's generator is independent — but
14
- * the output has to be one file, and re-encoding the parts to join them would give
15
- * back everything the parallelism won (and lose a generation of quality). Joining
16
- * at the packet level is what makes splitting worth doing.
17
- *
18
- * ## What the caller has to guarantee
19
- *
20
- * **Every segment must be encoded with an identical configuration** — same
21
- * container, codec, resolution and bitrate settings. A container carries exactly one decoder
22
- * configuration for a track, so a segment whose parameter sets differ from the
23
- * first would be described by a configuration that isn't its own, and decode as
24
- * garbage from that point on. That is not a theoretical concern: hardware encoders
25
- * are free to pick different parameter sets for the same nominal settings. This is
26
- * checked rather than assumed — see {@link ConcatMismatchError} — because the
27
- * failure is silent corruption rather than an exception.
28
- *
29
- * **Every segment must begin with a key frame.** Otherwise its first packets refer
30
- * back to frames that lived in the previous segment's GOP and no longer precede
31
- * them. Encoders start with a key frame by default; a caller choosing an explicit
32
- * `keyFrameInterval` needs it to divide the segment length.
33
- *
34
- * Audio is *muxed* here but never *joined*: the segments' own audio is ignored
35
- * entirely, and a single already-mixed buffer is encoded once across the whole
36
- * result (see {@link ConcatOptions.audio}). Concatenating separately-encoded AAC
37
- * would put the encoder's priming delay at every join — a click at each boundary
38
- * and a track that drifts further out of sync with every one — so the timeline's
39
- * audio is mixed in one pass by whoever owns the timeline, and arrives here as
40
- * one buffer.
14
+ * Audio is *muxed* here but never *joined*: the segments' own audio is ignored and one already-mixed buffer is encoded
15
+ * across the whole result (see {@link ConcatOptions.audio}), because separately-encoded AAC puts the encoder's priming
16
+ * delay at every join.
41
17
  */
42
18
  /** Thrown when segments can't be joined losslessly. Carries which segment disagreed. */
43
19
  export class ConcatMismatchError extends Error {
44
20
  segment;
45
- constructor(
46
- /** Index of the offending segment in the input array. */
47
- segment, message) {
21
+ constructor(segment, message) {
48
22
  super(message);
49
23
  this.segment = segment;
50
24
  this.name = "ConcatMismatchError";
51
25
  }
52
26
  }
53
- /**
54
- * Two decoder configurations describe the same bitstream.
55
- *
56
- * `description` holds the codec's parameter sets (the `avcC` box for H.264) and is
57
- * the field that actually decides decodability — a differing one is exactly the
58
- * silent-corruption case. Compared bytewise; the rest are compared by value.
59
- */
27
+ /** Two decoder configurations describe the same bitstream. `description` (parameter sets, the `avcC` box for H.264) decides decodability and is compared bytewise; the rest by value. */
60
28
  function sameDecoderConfig(a, b) {
61
29
  if (a.codec !== b.codec)
62
30
  return false;
@@ -87,15 +55,7 @@ function toBytes(description) {
87
55
  }
88
56
  return new Uint8Array(description);
89
57
  }
90
- /**
91
- * {@link concatVideoSegments} one segment at a time, so a split export can write
92
- * each piece the moment it and every piece before it exist, instead of holding
93
- * every segment until the last one lands.
94
- *
95
- * The audio, when given, is encoded as soon as the first segment fixes the
96
- * container, ahead of the video: the muxer holds the (small) audio packets and
97
- * interleaves them as the video catches up.
98
- */
58
+ /** {@link concatVideoSegments} one segment at a time, so a split export writes each piece as soon as it and every piece before it exist. Audio is encoded as soon as the first segment fixes the container, ahead of the video; the muxer interleaves it as the video catches up. */
99
59
  export function createSegmentJoiner(options = {}) {
100
60
  let first = null;
101
61
  let output = null;
@@ -109,9 +69,7 @@ export function createSegmentJoiner(options = {}) {
109
69
  ? new StreamTarget(options.stream)
110
70
  : new BufferTarget();
111
71
  output = new Output({ format: formatSpec(segment.format).container(), target });
112
- // `codec` comes off the track rather than the decoder config: the latter's
113
- // `codec` is the full codec *string* (`avc1.64001f`), while the source wants
114
- // the family.
72
+ // `codec` comes off the track: the decoder config's is the full codec *string* (`avc1.64001f`), the source wants the family.
115
73
  source = new EncodedVideoPacketSource(segment.track.codec);
116
74
  output.addVideoTrack(source);
117
75
  // Declared before `start()`: a muxer fixes its track list up front.
@@ -132,9 +90,7 @@ export function createSegmentJoiner(options = {}) {
132
90
  first = segment;
133
91
  await start(segment);
134
92
  }
135
- // The decoder config is handed over with each segment's first packet.
136
- // Only the first can take effect (a container holds one per track), but
137
- // every one has been checked equal to it.
93
+ // The decoder config rides each segment's first packet; only the first can take effect (one per track), but all were checked equal.
138
94
  let leading = true;
139
95
  for await (const packet of new EncodedPacketSink(segment.track).packets()) {
140
96
  await source.add(packet.clone({ timestamp: packet.timestamp + offset }), leading ? { decoderConfig: segment.config } : undefined);
@@ -187,16 +143,10 @@ async function openSegment(input, i, first) {
187
143
  return { format, track, config, duration: await track.computeDuration() };
188
144
  }
189
145
  /**
190
- * Concatenate `segments` (each a complete MP4, or each a complete WebM) into one
191
- * file of the same container.
192
- *
193
- * Segments are joined in array order. Returns the muxed bytes.
146
+ * Concatenates `segments` (all MP4 or all WebM) in array order into one file of the same container.
194
147
  *
195
- * @throws {ConcatMismatchError} if a segment carries no video track, is in another
196
- * container than the first, or has a decoder configuration that differs
197
- * from the first segment's. Callers that can
198
- * fall back to a single-threaded export should catch this and do so — the
199
- * alternative is shipping a file that decodes into garbage halfway through.
148
+ * @throws {ConcatMismatchError} if a segment has no video track, is in another container than the first, or has a
149
+ * differing decoder configuration. Callers that can fall back to a single-threaded export should catch this.
200
150
  */
201
151
  export async function concatVideoSegments(segments, options = {}) {
202
152
  if (segments.length === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"concat.js","sourceRoot":"","sources":["../src/concat.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BufferSource,\n BufferTarget,\n EncodedPacketSink,\n EncodedVideoPacketSource,\n Input,\n MP4,\n Output,\n StreamTarget,\n WEBM,\n type InputVideoTrack,\n type VideoCodec,\n} from \"mediabunny\";\n\nimport { audioTrackSource, formatSpec, type VideoFormat } from \"./formats\";\n\n/**\n * Join encoded MP4 or WebM segments end to end, without re-encoding.\n *\n * The point of this is what it *doesn't* do: no decode, no encode, no pixels.\n * Each segment is demuxed to its encoded packets, the packets are re-stamped onto\n * one continuous timeline, and a single muxer writes them into one container. The\n * bytes of every frame survive untouched, so the result is bit-identical to the\n * segments and the whole thing runs at I/O speed rather than encode speed.\n *\n * It exists so an export can be **split across workers**. Rendering a timeline is\n * embarrassingly parallel per scene — each scene's generator is independent — but\n * the output has to be one file, and re-encoding the parts to join them would give\n * back everything the parallelism won (and lose a generation of quality). Joining\n * at the packet level is what makes splitting worth doing.\n *\n * ## What the caller has to guarantee\n *\n * **Every segment must be encoded with an identical configuration** — same\n * container, codec, resolution and bitrate settings. A container carries exactly one decoder\n * configuration for a track, so a segment whose parameter sets differ from the\n * first would be described by a configuration that isn't its own, and decode as\n * garbage from that point on. That is not a theoretical concern: hardware encoders\n * are free to pick different parameter sets for the same nominal settings. This is\n * checked rather than assumed — see {@link ConcatMismatchError} — because the\n * failure is silent corruption rather than an exception.\n *\n * **Every segment must begin with a key frame.** Otherwise its first packets refer\n * back to frames that lived in the previous segment's GOP and no longer precede\n * them. Encoders start with a key frame by default; a caller choosing an explicit\n * `keyFrameInterval` needs it to divide the segment length.\n *\n * Audio is *muxed* here but never *joined*: the segments' own audio is ignored\n * entirely, and a single already-mixed buffer is encoded once across the whole\n * result (see {@link ConcatOptions.audio}). Concatenating separately-encoded AAC\n * would put the encoder's priming delay at every join — a click at each boundary\n * and a track that drifts further out of sync with every one — so the timeline's\n * audio is mixed in one pass by whoever owns the timeline, and arrives here as\n * one buffer.\n */\n\n/** Thrown when segments can't be joined losslessly. Carries which segment disagreed. */\nexport class ConcatMismatchError extends Error {\n constructor(\n /** Index of the offending segment in the input array. */\n readonly segment: number,\n message: string,\n ) {\n super(message);\n this.name = \"ConcatMismatchError\";\n }\n}\n\n/** One segment's demuxed video track, plus the config that describes it. */\ninterface Segment {\n format: VideoFormat;\n track: InputVideoTrack;\n config: VideoDecoderConfig;\n duration: number;\n}\n\n/**\n * Two decoder configurations describe the same bitstream.\n *\n * `description` holds the codec's parameter sets (the `avcC` box for H.264) and is\n * the field that actually decides decodability — a differing one is exactly the\n * silent-corruption case. Compared bytewise; the rest are compared by value.\n */\nfunction sameDecoderConfig(a: VideoDecoderConfig, b: VideoDecoderConfig): boolean {\n if (a.codec !== b.codec) return false;\n if (a.codedWidth !== b.codedWidth) return false;\n if (a.codedHeight !== b.codedHeight) return false;\n\n const da = toBytes(a.description);\n const db = toBytes(b.description);\n if (da === null || db === null) return da === db;\n if (da.length !== db.length) return false;\n for (let i = 0; i < da.length; i++) {\n if (da[i] !== db[i]) return false;\n }\n return true;\n}\n\nfunction toBytes(description: VideoDecoderConfig[\"description\"]): Uint8Array | null {\n if (!description) return null;\n // `description` is a BufferSource: an ArrayBuffer, a SharedArrayBuffer, or any\n // view over one. Views carry an offset into a larger buffer, so they must be\n // narrowed rather than wrapped whole.\n if (ArrayBuffer.isView(description)) {\n return new Uint8Array(description.buffer, description.byteOffset, description.byteLength);\n }\n return new Uint8Array(description);\n}\n\nexport interface ConcatOptions {\n /**\n * One continuous audio track for the whole joined timeline.\n *\n * Mixed once by the caller (see `mixTimelineAudio`) rather than per segment,\n * and that is not a convenience: separately-encoded AAC carries an encoder\n * priming delay at the head of each piece, so joining them puts a click at\n * every scene boundary and drifts the track further out of sync with each\n * one. One buffer in, one encode, no seams.\n */\n audio?: AudioBuffer | null;\n /** Bitrate for that track: AAC in an MP4, Opus in a WebM. Defaults to 192 kbps. */\n audioBitrate?: number;\n /**\n * Write the result out as it is muxed instead of returning it.\n *\n * With this the joined file never exists in memory as one buffer, which is\n * the difference between an export the process can hold and one it can't:\n * a 4× 60fps render is hundreds of megabytes, and the default path costs\n * roughly twice that at the tail once the `Blob` copy is counted.\n *\n * **Chunks carry a byte `position` and do not arrive in file order** — a\n * muxer seeks back to patch atom sizes. A sink that appends will produce a\n * file of exactly the right length whose size fields belong to a different\n * one: unplayable, and indistinguishable from success until it is opened.\n * Honour `position`.\n *\n * `concatVideoSegments` resolves to an empty array when this is used; the\n * bytes went to the sink.\n */\n stream?: WritableStream<{ type: \"write\"; data: Uint8Array; position: number }>;\n}\n\n/** Joins segments one at a time, in timeline order, as each becomes available. */\nexport interface SegmentJoiner {\n /**\n * Mux the next segment after the ones already appended. Appends must not\n * overlap: await each before starting the next.\n *\n * @throws {ConcatMismatchError} as {@link concatVideoSegments} does.\n */\n append(segment: Uint8Array): Promise<void>;\n /** Finish the container. The bytes, or an empty array when they went to `stream`. */\n finish(): Promise<Uint8Array>;\n /** Abandon the join; nothing more is written. */\n cancel(): Promise<void>;\n}\n\n/**\n * {@link concatVideoSegments} one segment at a time, so a split export can write\n * each piece the moment it and every piece before it exist, instead of holding\n * every segment until the last one lands.\n *\n * The audio, when given, is encoded as soon as the first segment fixes the\n * container, ahead of the video: the muxer holds the (small) audio packets and\n * interleaves them as the video catches up.\n */\nexport function createSegmentJoiner(options: ConcatOptions = {}): SegmentJoiner {\n let first: Segment | null = null;\n let output: Output | null = null;\n let source: EncodedVideoPacketSource | null = null;\n let audioSource: ReturnType<typeof audioTrackSource> | null = null;\n let target: StreamTarget | BufferTarget | null = null;\n let index = 0;\n let offset = 0;\n\n const start = async (segment: Segment) => {\n target = options.stream\n ? new StreamTarget(options.stream as ConstructorParameters<typeof StreamTarget>[0])\n : new BufferTarget();\n output = new Output({ format: formatSpec(segment.format).container(), target });\n // `codec` comes off the track rather than the decoder config: the latter's\n // `codec` is the full codec *string* (`avc1.64001f`), while the source wants\n // the family.\n source = new EncodedVideoPacketSource(segment.track.codec as VideoCodec);\n output.addVideoTrack(source);\n // Declared before `start()`: a muxer fixes its track list up front.\n audioSource = options.audio ? audioTrackSource(segment.format, options.audioBitrate) : null;\n if (audioSource) output.addAudioTrack(audioSource);\n await output.start();\n if (audioSource && options.audio) await audioSource.add(options.audio);\n };\n\n return {\n async append(bytes) {\n const i = index++;\n const input = new Input({ source: new BufferSource(bytes), formats: [MP4, WEBM] });\n try {\n const segment = await openSegment(input, i, first);\n if (!first) {\n first = segment;\n await start(segment);\n }\n // The decoder config is handed over with each segment's first packet.\n // Only the first can take effect (a container holds one per track), but\n // every one has been checked equal to it.\n let leading = true;\n for await (const packet of new EncodedPacketSink(segment.track).packets()) {\n await source!.add(\n packet.clone({ timestamp: packet.timestamp + offset }),\n leading ? { decoderConfig: segment.config } : undefined,\n );\n leading = false;\n }\n // The segment's own duration rather than its last packet's end: summing\n // packet durations accumulates rounding into a drift across many segments.\n offset += segment.duration;\n } finally {\n input.dispose();\n }\n },\n async finish() {\n if (!output) throw new Error(\"concatVideoSegments: no segments\");\n source!.close();\n audioSource?.close();\n await output.finalize();\n if (options.stream) return new Uint8Array(0);\n const buffer = (target as BufferTarget).buffer;\n if (!buffer) throw new Error(\"concatVideoSegments produced no data\");\n return new Uint8Array(buffer);\n },\n async cancel() {\n if (output && output.state !== \"finalized\" && output.state !== \"canceled\") await output.cancel();\n },\n };\n}\n\n/** Demux one segment and check it can follow `first`. */\nasync function openSegment(input: Input, i: number, first: Segment | null): Promise<Segment> {\n const format: VideoFormat = (await input.getFormat()) === WEBM ? \"webm\" : \"mp4\";\n if (first && format !== first.format) {\n throw new ConcatMismatchError(i, `Segment ${i} is ${format} and segment 0 is ${first.format}.`);\n }\n const track = await input.getPrimaryVideoTrack();\n if (!track) throw new ConcatMismatchError(i, `Segment ${i} has no video track.`);\n const config = await track.getDecoderConfig();\n if (!config) throw new ConcatMismatchError(i, `Segment ${i} has no decoder configuration.`);\n if (first && !sameDecoderConfig(first.config, config)) {\n throw new ConcatMismatchError(\n i,\n `Segment ${i} was encoded differently from segment 0 (${first.config.codec} `\n + `${first.config.codedWidth}x${first.config.codedHeight} vs ${config.codec} `\n + `${config.codedWidth}x${config.codedHeight}); joining them would decode as garbage.`,\n );\n }\n return { format, track, config, duration: await track.computeDuration() };\n}\n\n/**\n * Concatenate `segments` (each a complete MP4, or each a complete WebM) into one\n * file of the same container.\n *\n * Segments are joined in array order. Returns the muxed bytes.\n *\n * @throws {ConcatMismatchError} if a segment carries no video track, is in another\n * container than the first, or has a decoder configuration that differs\n * from the first segment's. Callers that can\n * fall back to a single-threaded export should catch this and do so — the\n * alternative is shipping a file that decodes into garbage halfway through.\n */\nexport async function concatVideoSegments(\n segments: Uint8Array[],\n options: ConcatOptions = {},\n): Promise<Uint8Array> {\n if (segments.length === 0) throw new Error(\"concatVideoSegments: no segments\");\n // A lone segment is already the finished file — unless a track has to be added\n // to it, or it has to go to a sink, either of which means muxing after all.\n if (segments.length === 1 && !options.audio && !options.stream) return segments[0];\n\n const joiner = createSegmentJoiner(options);\n try {\n for (const segment of segments) await joiner.append(segment);\n return await joiner.finish();\n } catch (error) {\n await joiner.cancel().catch(() => {});\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"concat.js","sourceRoot":"","sources":["../src/concat.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BufferSource,\n BufferTarget,\n EncodedPacketSink,\n EncodedVideoPacketSource,\n Input,\n MP4,\n Output,\n StreamTarget,\n WEBM,\n type InputVideoTrack,\n type VideoCodec,\n} from \"mediabunny\";\n\nimport { audioTrackSource, formatSpec, type VideoFormat } from \"./formats\";\n\n/**\n * Joins encoded MP4 or WebM segments end to end without re-encoding: segments are demuxed to packets, re-stamped onto\n * one timeline and muxed into one container, so frame bytes survive untouched and it runs at I/O speed. It exists so an\n * export can be **split across workers** without re-encoding the parts (which would lose a generation of quality).\n *\n * The caller must guarantee: **every segment is encoded with an identical configuration** (container, codec,\n * resolution, bitrate settings), since a container carries one decoder configuration per track and a differing one\n * decodes as garbage from that point (hardware encoders may pick different parameter sets for the same settings; this\n * is checked, see {@link ConcatMismatchError}, because the failure is silent). And **every segment begins with a key\n * frame**; a caller choosing an explicit `keyFrameInterval` needs it to divide the segment length.\n *\n * Audio is *muxed* here but never *joined*: the segments' own audio is ignored and one already-mixed buffer is encoded\n * across the whole result (see {@link ConcatOptions.audio}), because separately-encoded AAC puts the encoder's priming\n * delay at every join.\n */\n\n/** Thrown when segments can't be joined losslessly. Carries which segment disagreed. */\nexport class ConcatMismatchError extends Error {\n constructor(\n readonly segment: number,\n message: string,\n ) {\n super(message);\n this.name = \"ConcatMismatchError\";\n }\n}\n\ninterface Segment {\n format: VideoFormat;\n track: InputVideoTrack;\n config: VideoDecoderConfig;\n duration: number;\n}\n\n/** Two decoder configurations describe the same bitstream. `description` (parameter sets, the `avcC` box for H.264) decides decodability and is compared bytewise; the rest by value. */\nfunction sameDecoderConfig(a: VideoDecoderConfig, b: VideoDecoderConfig): boolean {\n if (a.codec !== b.codec) return false;\n if (a.codedWidth !== b.codedWidth) return false;\n if (a.codedHeight !== b.codedHeight) return false;\n\n const da = toBytes(a.description);\n const db = toBytes(b.description);\n if (da === null || db === null) return da === db;\n if (da.length !== db.length) return false;\n for (let i = 0; i < da.length; i++) {\n if (da[i] !== db[i]) return false;\n }\n return true;\n}\n\nfunction toBytes(description: VideoDecoderConfig[\"description\"]): Uint8Array | null {\n if (!description) return null;\n // `description` is a BufferSource: an ArrayBuffer, a SharedArrayBuffer, or any\n // view over one. Views carry an offset into a larger buffer, so they must be\n // narrowed rather than wrapped whole.\n if (ArrayBuffer.isView(description)) {\n return new Uint8Array(description.buffer, description.byteOffset, description.byteLength);\n }\n return new Uint8Array(description);\n}\n\nexport interface ConcatOptions {\n /** One continuous audio track, mixed once by the caller (see `mixTimelineAudio`): separately-encoded AAC carries a priming delay per piece, so joining them clicks at every boundary and drifts out of sync. */\n audio?: AudioBuffer | null;\n /** Bitrate for that track: AAC in an MP4, Opus in a WebM. Defaults to 192 kbps. */\n audioBitrate?: number;\n /**\n * Write the result out as it is muxed instead of returning it, so the joined file never exists in memory as one\n * buffer (a 4x 60fps render is hundreds of megabytes, twice that at the tail once the `Blob` copy is counted).\n *\n * **Chunks carry a byte `position` and do not arrive in file order** (a muxer seeks back to patch atom sizes). A sink\n * that appends produces a file of the right length that is unplayable, indistinguishable from success until opened.\n * Honour `position`. `concatVideoSegments` resolves to an empty array when this is used.\n */\n stream?: WritableStream<{ type: \"write\"; data: Uint8Array; position: number }>;\n}\n\n/** Joins segments one at a time, in timeline order, as each becomes available. */\nexport interface SegmentJoiner {\n /**\n * Appends must not overlap: await each before starting the next.\n *\n * @throws {ConcatMismatchError} as {@link concatVideoSegments} does.\n */\n append(segment: Uint8Array): Promise<void>;\n /** Finish the container. The bytes, or an empty array when they went to `stream`. */\n finish(): Promise<Uint8Array>;\n /** Abandon the join; nothing more is written. */\n cancel(): Promise<void>;\n}\n\n/** {@link concatVideoSegments} one segment at a time, so a split export writes each piece as soon as it and every piece before it exist. Audio is encoded as soon as the first segment fixes the container, ahead of the video; the muxer interleaves it as the video catches up. */\nexport function createSegmentJoiner(options: ConcatOptions = {}): SegmentJoiner {\n let first: Segment | null = null;\n let output: Output | null = null;\n let source: EncodedVideoPacketSource | null = null;\n let audioSource: ReturnType<typeof audioTrackSource> | null = null;\n let target: StreamTarget | BufferTarget | null = null;\n let index = 0;\n let offset = 0;\n\n const start = async (segment: Segment) => {\n target = options.stream\n ? new StreamTarget(options.stream as ConstructorParameters<typeof StreamTarget>[0])\n : new BufferTarget();\n output = new Output({ format: formatSpec(segment.format).container(), target });\n // `codec` comes off the track: the decoder config's is the full codec *string* (`avc1.64001f`), the source wants the family.\n source = new EncodedVideoPacketSource(segment.track.codec as VideoCodec);\n output.addVideoTrack(source);\n // Declared before `start()`: a muxer fixes its track list up front.\n audioSource = options.audio ? audioTrackSource(segment.format, options.audioBitrate) : null;\n if (audioSource) output.addAudioTrack(audioSource);\n await output.start();\n if (audioSource && options.audio) await audioSource.add(options.audio);\n };\n\n return {\n async append(bytes) {\n const i = index++;\n const input = new Input({ source: new BufferSource(bytes), formats: [MP4, WEBM] });\n try {\n const segment = await openSegment(input, i, first);\n if (!first) {\n first = segment;\n await start(segment);\n }\n // The decoder config rides each segment's first packet; only the first can take effect (one per track), but all were checked equal.\n let leading = true;\n for await (const packet of new EncodedPacketSink(segment.track).packets()) {\n await source!.add(\n packet.clone({ timestamp: packet.timestamp + offset }),\n leading ? { decoderConfig: segment.config } : undefined,\n );\n leading = false;\n }\n // The segment's own duration rather than its last packet's end: summing\n // packet durations accumulates rounding into a drift across many segments.\n offset += segment.duration;\n } finally {\n input.dispose();\n }\n },\n async finish() {\n if (!output) throw new Error(\"concatVideoSegments: no segments\");\n source!.close();\n audioSource?.close();\n await output.finalize();\n if (options.stream) return new Uint8Array(0);\n const buffer = (target as BufferTarget).buffer;\n if (!buffer) throw new Error(\"concatVideoSegments produced no data\");\n return new Uint8Array(buffer);\n },\n async cancel() {\n if (output && output.state !== \"finalized\" && output.state !== \"canceled\") await output.cancel();\n },\n };\n}\n\n/** Demux one segment and check it can follow `first`. */\nasync function openSegment(input: Input, i: number, first: Segment | null): Promise<Segment> {\n const format: VideoFormat = (await input.getFormat()) === WEBM ? \"webm\" : \"mp4\";\n if (first && format !== first.format) {\n throw new ConcatMismatchError(i, `Segment ${i} is ${format} and segment 0 is ${first.format}.`);\n }\n const track = await input.getPrimaryVideoTrack();\n if (!track) throw new ConcatMismatchError(i, `Segment ${i} has no video track.`);\n const config = await track.getDecoderConfig();\n if (!config) throw new ConcatMismatchError(i, `Segment ${i} has no decoder configuration.`);\n if (first && !sameDecoderConfig(first.config, config)) {\n throw new ConcatMismatchError(\n i,\n `Segment ${i} was encoded differently from segment 0 (${first.config.codec} `\n + `${first.config.codedWidth}x${first.config.codedHeight} vs ${config.codec} `\n + `${config.codedWidth}x${config.codedHeight}); joining them would decode as garbage.`,\n );\n }\n return { format, track, config, duration: await track.computeDuration() };\n}\n\n/**\n * Concatenates `segments` (all MP4 or all WebM) in array order into one file of the same container.\n *\n * @throws {ConcatMismatchError} if a segment has no video track, is in another container than the first, or has a\n * differing decoder configuration. Callers that can fall back to a single-threaded export should catch this.\n */\nexport async function concatVideoSegments(\n segments: Uint8Array[],\n options: ConcatOptions = {},\n): Promise<Uint8Array> {\n if (segments.length === 0) throw new Error(\"concatVideoSegments: no segments\");\n // A lone segment is already the finished file — unless a track has to be added\n // to it, or it has to go to a sink, either of which means muxing after all.\n if (segments.length === 1 && !options.audio && !options.stream) return segments[0];\n\n const joiner = createSegmentJoiner(options);\n try {\n for (const segment of segments) await joiner.append(segment);\n return await joiner.finish();\n } catch (error) {\n await joiner.cancel().catch(() => {});\n throw error;\n }\n}\n"]}
@@ -1,9 +1,4 @@
1
1
  import type { DynamicNodeConfig } from "@motionscript/core";
2
- /**
3
- * Resolves a full `DynamicNodeConfig`, filling in whatever the caller left
4
- * unset with `browser`'s own default. Synchronous — the WASM import happens
5
- * on `compile`'s first actual call, not here, so attaching this to every
6
- * engine costs nothing until a source string is genuinely registered.
7
- */
2
+ /** Resolves a full `DynamicNodeConfig`, filling unset fields with `browser`'s default. Synchronous: the WASM import happens on `compile`'s first call, so attaching this to every engine is free until a source string is registered. */
8
3
  export declare function resolveDynamicNodes(partial: Partial<DynamicNodeConfig> | undefined, compilerWasmUrl?: string): DynamicNodeConfig;
9
4
  //# sourceMappingURL=dynamic-nodes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-nodes.d.ts","sourceRoot":"","sources":["../src/dynamic-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA6C5D;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,SAAS,EAC/C,eAAe,CAAC,EAAE,MAAM,GACzB,iBAAiB,CAMnB"}
1
+ {"version":3,"file":"dynamic-nodes.d.ts","sourceRoot":"","sources":["../src/dynamic-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA6B5D,yOAAyO;AACzO,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,SAAS,EAC/C,eAAe,CAAC,EAAE,MAAM,GACzB,iBAAiB,CAMnB"}
@@ -1,23 +1,7 @@
1
1
  import * as sdk from "@motionscript/core/sdk";
2
2
  import * as esbuild from "esbuild-wasm";
3
- /**
4
- * `browser`'s real, working default for `Registry.addNodeFromSource` — the
5
- * `sdk` half is cheap (re-exports, no WASM) and imported eagerly; the compiler
6
- * is the expensive half, and stays behind a lazy `import()` so an app that
7
- * never registers a source-string node never pays for it, however long the
8
- * engine has been running.
9
- *
10
- * `core` has no compiler dependency of its own and cannot import the published
11
- * `@motionscript/sdk` barrel (that package depends on `core`, so the reverse
12
- * would be circular) — `browser` is where this concern actually belongs: the
13
- * package whose entire job is "what running in an actual browser tab looks
14
- * like" already owns the renderer, `WebAudioSink` and `WebTicker` on exactly that
15
- * basis.
16
- */
17
- // Default to the wasm shipped by esbuild-wasm, resolved lazily — `import.meta.url`
18
- // is empty in a non-ESM output (an IIFE bundle, which is what the headless page
19
- // loads), and `new URL("…", "")` throws on import, before a caller has had the
20
- // chance to pass an explicit URL that would have made the default irrelevant.
3
+ /** `browser`'s working default for `Registry.addNodeFromSource`: the cheap `sdk` half is imported eagerly, the compiler stays behind a lazy `import()` so an app that never registers a source-string node never pays for it. `core` cannot import the published `@motionscript/sdk` barrel (circular) and has no compiler dependency, so the concern lives here, which already owns the renderer, `WebAudioSink` and `WebTicker`. */
4
+ // Default to the wasm shipped by esbuild-wasm, resolved lazily: `import.meta.url` is empty in a non-ESM output (the headless page's IIFE), and `new URL("…", "")` would throw on import before a caller could pass an explicit URL.
21
5
  function defaultCompilerWasmUrl() {
22
6
  return new URL("esbuild-wasm/esbuild.wasm", import.meta.url).href;
23
7
  }
@@ -37,12 +21,7 @@ function defaultCompiler(wasmUrl) {
37
21
  };
38
22
  })());
39
23
  }
40
- /**
41
- * Resolves a full `DynamicNodeConfig`, filling in whatever the caller left
42
- * unset with `browser`'s own default. Synchronous — the WASM import happens
43
- * on `compile`'s first actual call, not here, so attaching this to every
44
- * engine costs nothing until a source string is genuinely registered.
45
- */
24
+ /** Resolves a full `DynamicNodeConfig`, filling unset fields with `browser`'s default. Synchronous: the WASM import happens on `compile`'s first call, so attaching this to every engine is free until a source string is registered. */
46
25
  export function resolveDynamicNodes(partial, compilerWasmUrl) {
47
26
  return {
48
27
  sdk: partial?.sdk ?? sdk,
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-nodes.js","sourceRoot":"","sources":["../src/dynamic-nodes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,wBAAwB,CAAC;AAC9C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC;;;;;;;;;;;;;GAaG;AAEH,mFAAmF;AACnF,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,SAAS,sBAAsB;IAC3B,OAAO,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,IAAI,eAAyE,CAAC;AAE9E,0CAA0C;AAC1C,SAAS,eAAe,CAAC,OAAgB;IACrC,OAAO,CAAC,eAAe,KAAK,CAAC,KAAK,IAAI,EAAE;QACpC,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,EAAE,CAAC,CAAC;QAC3E,OAAO,KAAK,EAAE,MAAc,EAAE,EAAE;YAC5B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE;gBAC7C,iEAAiE;gBACjE,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,QAAQ;aACnB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QAChB,CAAC,CAAC;IACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACV,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAC/B,OAA+C,EAC/C,eAAwB;IAExB,OAAO;QACH,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG;QACxB,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;KACpG,CAAC;AACN,CAAC","sourcesContent":["import type { DynamicNodeConfig } from \"@motionscript/core\";\nimport * as sdk from \"@motionscript/core/sdk\";\nimport * as esbuild from \"esbuild-wasm\";\n\n/**\n * `browser`'s real, working default for `Registry.addNodeFromSource` — the\n * `sdk` half is cheap (re-exports, no WASM) and imported eagerly; the compiler\n * is the expensive half, and stays behind a lazy `import()` so an app that\n * never registers a source-string node never pays for it, however long the\n * engine has been running.\n *\n * `core` has no compiler dependency of its own and cannot import the published\n * `@motionscript/sdk` barrel (that package depends on `core`, so the reverse\n * would be circular) — `browser` is where this concern actually belongs: the\n * package whose entire job is \"what running in an actual browser tab looks\n * like\" already owns the renderer, `WebAudioSink` and `WebTicker` on exactly that\n * basis.\n */\n\n// Default to the wasm shipped by esbuild-wasm, resolved lazily — `import.meta.url`\n// is empty in a non-ESM output (an IIFE bundle, which is what the headless page\n// loads), and `new URL(\"…\", \"\")` throws on import, before a caller has had the\n// chance to pass an explicit URL that would have made the default irrelevant.\nfunction defaultCompilerWasmUrl(): string {\n return new URL(\"esbuild-wasm/esbuild.wasm\", import.meta.url).href;\n}\n\nlet compilerPromise: Promise<(source: string) => Promise<string>> | undefined;\n\n/** Loaded once, memoized process-wide. */\nfunction defaultCompiler(wasmUrl?: string): Promise<(source: string) => Promise<string>> {\n return (compilerPromise ??= (async () => {\n await esbuild.initialize({ wasmURL: wasmUrl ?? defaultCompilerWasmUrl() });\n return async (source: string) => {\n const { code } = await esbuild.transform(source, {\n // A component is written with calls: there is no JSX to compile.\n loader: \"ts\",\n format: \"cjs\",\n target: \"es2020\",\n });\n return code;\n };\n })());\n}\n\n/**\n * Resolves a full `DynamicNodeConfig`, filling in whatever the caller left\n * unset with `browser`'s own default. Synchronous — the WASM import happens\n * on `compile`'s first actual call, not here, so attaching this to every\n * engine costs nothing until a source string is genuinely registered.\n */\nexport function resolveDynamicNodes(\n partial: Partial<DynamicNodeConfig> | undefined,\n compilerWasmUrl?: string,\n): DynamicNodeConfig {\n return {\n sdk: partial?.sdk ?? sdk,\n modules: partial?.modules,\n compile: partial?.compile ?? (async (source) => (await defaultCompiler(compilerWasmUrl))(source)),\n };\n}\n"]}
1
+ {"version":3,"file":"dynamic-nodes.js","sourceRoot":"","sources":["../src/dynamic-nodes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,wBAAwB,CAAC;AAC9C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC,saAAsa;AAEta,oOAAoO;AACpO,SAAS,sBAAsB;IAC3B,OAAO,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,IAAI,eAAyE,CAAC;AAE9E,0CAA0C;AAC1C,SAAS,eAAe,CAAC,OAAgB;IACrC,OAAO,CAAC,eAAe,KAAK,CAAC,KAAK,IAAI,EAAE;QACpC,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,EAAE,CAAC,CAAC;QAC3E,OAAO,KAAK,EAAE,MAAc,EAAE,EAAE;YAC5B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE;gBAC7C,iEAAiE;gBACjE,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,QAAQ;aACnB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QAChB,CAAC,CAAC;IACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACV,CAAC;AAED,yOAAyO;AACzO,MAAM,UAAU,mBAAmB,CAC/B,OAA+C,EAC/C,eAAwB;IAExB,OAAO;QACH,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG;QACxB,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;KACpG,CAAC;AACN,CAAC","sourcesContent":["import type { DynamicNodeConfig } from \"@motionscript/core\";\nimport * as sdk from \"@motionscript/core/sdk\";\nimport * as esbuild from \"esbuild-wasm\";\n\n/** `browser`'s working default for `Registry.addNodeFromSource`: the cheap `sdk` half is imported eagerly, the compiler stays behind a lazy `import()` so an app that never registers a source-string node never pays for it. `core` cannot import the published `@motionscript/sdk` barrel (circular) and has no compiler dependency, so the concern lives here, which already owns the renderer, `WebAudioSink` and `WebTicker`. */\n\n// Default to the wasm shipped by esbuild-wasm, resolved lazily: `import.meta.url` is empty in a non-ESM output (the headless page's IIFE), and `new URL(\"…\", \"\")` would throw on import before a caller could pass an explicit URL.\nfunction defaultCompilerWasmUrl(): string {\n return new URL(\"esbuild-wasm/esbuild.wasm\", import.meta.url).href;\n}\n\nlet compilerPromise: Promise<(source: string) => Promise<string>> | undefined;\n\n/** Loaded once, memoized process-wide. */\nfunction defaultCompiler(wasmUrl?: string): Promise<(source: string) => Promise<string>> {\n return (compilerPromise ??= (async () => {\n await esbuild.initialize({ wasmURL: wasmUrl ?? defaultCompilerWasmUrl() });\n return async (source: string) => {\n const { code } = await esbuild.transform(source, {\n // A component is written with calls: there is no JSX to compile.\n loader: \"ts\",\n format: \"cjs\",\n target: \"es2020\",\n });\n return code;\n };\n })());\n}\n\n/** Resolves a full `DynamicNodeConfig`, filling unset fields with `browser`'s default. Synchronous: the WASM import happens on `compile`'s first call, so attaching this to every engine is free until a source string is registered. */\nexport function resolveDynamicNodes(\n partial: Partial<DynamicNodeConfig> | undefined,\n compilerWasmUrl?: string,\n): DynamicNodeConfig {\n return {\n sdk: partial?.sdk ?? sdk,\n modules: partial?.modules,\n compile: partial?.compile ?? (async (source) => (await defaultCompiler(compilerWasmUrl))(source)),\n };\n}\n"]}
package/dist/engine.d.ts CHANGED
@@ -4,28 +4,11 @@ import { type RendererWasmOption } from "./renderer";
4
4
  import { type AudioEncoderOptions, type VideoEncoderOptions } from "./exporter";
5
5
  import { type ExportFormat } from "./formats";
6
6
  export interface BrowserEngineOptions extends Omit<EngineOptions, "nodes" | "dynamicNodes">, RendererWasmOption {
7
- /**
8
- * Node types any document opened here may name — components as an array
9
- * or a record naming each key outright, same as `EngineOptions`, plus a
10
- * TS source string in the **array** form only. A string default-exports a
11
- * component, whose `key` is its key — the record form takes no string, so
12
- * there is exactly one place a dynamic node's key comes from, not two that
13
- * could disagree.
14
- */
7
+ /** Node types any document opened here may name: components as an array or a record, plus a TS source string in the **array** form only (a string default-exports a component whose `key` is its key, so a dynamic node's key has one source). */
15
8
  nodes?: readonly (NodeConstructor | string)[] | NodeTypeMap;
16
- /**
17
- * Enables a source string in `nodes`, and `engine.registerNode(source)`
18
- * at any point afterward. Any field left unset falls back to `browser`'s
19
- * own default — `@motionscript/core/sdk` for `sdk`, a lazily-loaded
20
- * `esbuild-wasm` for `compile` — so this never needs to be passed at all
21
- * for the common case. `modules` has no default: only the host knows
22
- * which loaded packages a script it compiles may reach.
23
- */
9
+ /** Enables a source string in `nodes` and `engine.registerNode(source)`. Unset fields fall back to `browser`'s defaults (`@motionscript/core/sdk`, a lazy `esbuild-wasm`); `modules` has none, since only the host knows which packages a script may reach. */
24
10
  dynamicNodes?: Partial<DynamicNodeConfig>;
25
- /**
26
- * Where `esbuild-wasm`'s own `.wasm` binary is served — only read if the
27
- * default compiler is actually used.
28
- */
11
+ /** Where `esbuild-wasm`'s `.wasm` is served; only read if the default compiler is used. */
29
12
  compilerWasmUrl?: string;
30
13
  /** Default video encoder settings for `session.export()`. Overridable per call. */
31
14
  video?: VideoEncoderOptions;
@@ -41,25 +24,13 @@ export interface BrowserRenderer<TStorage extends StorageAdapter = StorageAdapte
41
24
  }
42
25
  /** A render context as this package drives it: an engine one, plus what exports and stills ask of it. */
43
26
  export type BrowserRenderContext = EngineRenderContext & {
44
- /**
45
- * Settles once the surface `mount` began can draw. A backend whose surface
46
- * comes up asynchronously (a GPU device) must have it, or the first frame
47
- * draws onto nothing.
48
- */
27
+ /** Settles once the surface `mount` began can draw; a backend with an asynchronous surface (a GPU device) must have it or the first frame draws onto nothing. */
49
28
  whenMounted?(): Promise<void>;
50
- /**
51
- * The frame last drawn, copied into a fresh 2D canvas, or `undefined` before
52
- * one was. What a still encodes; a WebGPU canvas is readable only in the task
53
- * that drew it, so a backend may draw it again here.
54
- */
29
+ /** The frame last drawn, copied into a fresh 2D canvas (what a still encodes), or `undefined` before one was. A WebGPU canvas is readable only in the task that drew it, so a backend may draw again here. */
55
30
  snapshotCanvas?(): HTMLCanvasElement | undefined;
56
31
  };
57
- /** What `session.export({ encode })` accepts in this environment. */
58
32
  export interface BrowserEncodeOptions {
59
- /**
60
- * Defaults to `'mp4'`. An audio format (`'wav'`, `'mp3'`, `'m4a'`) renders no
61
- * frames and ignores `scale`, `frames`, `includeAudio`, `video` and `supersample`.
62
- */
33
+ /** Defaults to `'mp4'`. An audio format (`'wav'`, `'mp3'`, `'m4a'`) renders no frames and ignores `scale`, `frames`, `includeAudio`, `video` and `supersample`. */
63
34
  format?: ExportFormat;
64
35
  video?: VideoEncoderOptions;
65
36
  audio?: AudioEncoderOptions;
@@ -69,13 +40,9 @@ export interface BrowserEncodeOptions {
69
40
  filename?: string;
70
41
  }
71
42
  /**
72
- * An engine backed by the browser: WebGPU surfaces, Web Audio, WebCodecs.
73
- * Rejects with `WebGpuUnavailableError` where this browser has no WebGPU adapter.
74
- *
75
- * Async because the renderer is a wasm module and every seam below needs it. Await
76
- * once, at app start, and share the result — the caches underneath (registered
77
- * fonts, decoded images, shaped paragraphs) are the point of keeping one engine
78
- * rather than one per canvas.
43
+ * An engine backed by the browser: WebGPU surfaces, Web Audio, WebCodecs. Rejects with `WebGpuUnavailableError` where
44
+ * there is no WebGPU adapter. Async because the renderer is a wasm module; await once at app start and share the
45
+ * result, since the caches underneath (fonts, decoded images, shaped paragraphs) are the point of one engine.
79
46
  *
80
47
  * ```ts
81
48
  * const engine = await createBrowserEngine({ nodes: { pulseRing: PulseRing } });
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,aAAa,EAEb,mBAAmB,EAOnB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,WAAW,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAInE,OAAO,EAAkC,KAAK,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEhH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAG7D,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,aAAa,EAAE,OAAO,GAAG,cAAc,CAAC,EAAE,kBAAkB;IAC3G;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,GAAG,WAAW,CAAC;IAC5D;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IAC7E,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,oBAAoB,CAAC;IAC7D,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,gBAAgB,CAAC;IAC1D,OAAO,CAAC,IAAI,IAAI,CAAC;CACpB;AAED,yGAAyG;AACzG,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IACrD;;;;OAIG;IACH,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;OAIG;IACH,cAAc,CAAC,IAAI,iBAAiB,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AA2HD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAsDrC"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,aAAa,EAEb,mBAAmB,EAOnB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,WAAW,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAInE,OAAO,EAAkC,KAAK,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEhH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAG7D,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,aAAa,EAAE,OAAO,GAAG,cAAc,CAAC,EAAE,kBAAkB;IAC3G,kPAAkP;IAClP,KAAK,CAAC,EAAE,SAAS,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,GAAG,WAAW,CAAC;IAC5D,+PAA+P;IAC/P,YAAY,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IAC7E,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,oBAAoB,CAAC;IAC7D,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,gBAAgB,CAAC;IAC1D,OAAO,CAAC,IAAI,IAAI,CAAC;CACpB;AAED,yGAAyG;AACzG,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IACrD,iKAAiK;IACjK,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,8MAA8M;IAC9M,cAAc,CAAC,IAAI,iBAAiB,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACjC,mKAAmK;IACnK,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAuGD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CA4CrC"}