@sequio/server 0.1.0 → 0.1.2

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.
@@ -0,0 +1,31 @@
1
+ import { AudioExportFormat } from '@sequio/engine';
2
+ import { AssetLoader, Externals, RuntimeBundle } from '@sequio/runtime';
3
+ export interface ExportBundleAudioNodeOptions {
4
+ /** Output file path. The extension is corrected to match the audio format. */
5
+ out: string;
6
+ /** Audio container format. @default 'mp3' */
7
+ format?: AudioExportFormat;
8
+ /** Audio codec (Mediabunny name). Defaults per format (aac / mp3 / pcm-s16 / opus). */
9
+ codec?: string;
10
+ /** Target audio bitrate, bits/sec. Ignored for `wav` (PCM). @default 128_000 */
11
+ bitrate?: number;
12
+ /** Extra bare modules the composition may `import` (e.g. `gsap`); see {@link renderBundleToFile}. */
13
+ externals?: Externals;
14
+ /** Resolver for local media assets (`loadAsset('./song.mp3')`); see {@link renderBundleToFile}. */
15
+ loadAsset?: AssetLoader;
16
+ }
17
+ export interface ExportBundleAudioNodeResult {
18
+ /** The written file path (extension corrected to the audio format). */
19
+ out: string;
20
+ format: AudioExportFormat;
21
+ codec: string;
22
+ /** Timeline duration mixed, in seconds. */
23
+ duration: number;
24
+ bytes: number;
25
+ }
26
+ /**
27
+ * Compile + run `bundle` and write its audio-only mix to `opts.out`. The built
28
+ * graph is disposed before returning.
29
+ */
30
+ export declare function exportBundleAudioToFile(bundle: RuntimeBundle, opts: ExportBundleAudioNodeOptions): Promise<ExportBundleAudioNodeResult>;
31
+ //# sourceMappingURL=audio-node.d.ts.map
@@ -14,4 +14,5 @@ export { renderTimelineToFile, renderFrameRGBA, type NodeExportOptions } from '.
14
14
  export { loadFontsNode, bridgeFontManagerToNode } from './fonts-node';
15
15
  export { renderBundleToFile, type RenderBundleNodeOptions, type RenderBundleNodeResult, } from './render-bundle';
16
16
  export { renderBundleFrameToFile, type RenderFrameNodeOptions, type RenderFrameNodeResult, } from './frame-node';
17
+ export { exportBundleAudioToFile, type ExportBundleAudioNodeOptions, type ExportBundleAudioNodeResult, } from './audio-node';
17
18
  //# sourceMappingURL=index.d.ts.map
package/dist/route-b.js CHANGED
@@ -1,143 +1,143 @@
1
- import { createRequire as J } from "node:module";
2
- import { exportFrameTimes as Q, FontManager as X, buildGoogleCss2Url as Y } from "@sequio/engine";
3
- import { Runtime as H } from "@sequio/runtime";
4
- let W = !1;
5
- const L = Symbol("napi-canvas");
6
- let I = null;
7
- function K() {
8
- if (!I) throw new Error("getMediabunny() before setupNodeEnvironment()");
9
- return I;
1
+ import { createRequire as X } from "node:module";
2
+ import { exportFrameTimes as J, FontManager as Q, buildGoogleCss2Url as Y, Exporter as K } from "@sequio/engine";
3
+ import { Runtime as W } from "@sequio/runtime";
4
+ let V = !1;
5
+ const k = Symbol("napi-canvas");
6
+ let O = null;
7
+ function Z() {
8
+ if (!O) throw new Error("getMediabunny() before setupNodeEnvironment()");
9
+ return O;
10
10
  }
11
- async function q() {
12
- if (W) return;
13
- const { JSDOM: a } = await import("jsdom"), e = new a("<!doctype html><html><body></body></html>");
14
- for (const o of ["window", "document", "navigator", "HTMLElement", "Node", "DOMParser"])
15
- if (globalThis[o] === void 0 && e.window[o] !== void 0)
11
+ async function N() {
12
+ if (V) return;
13
+ const { JSDOM: o } = await import("jsdom"), e = new o("<!doctype html><html><body></body></html>");
14
+ for (const a of ["window", "document", "navigator", "HTMLElement", "Node", "DOMParser"])
15
+ if (globalThis[a] === void 0 && e.window[a] !== void 0)
16
16
  try {
17
- globalThis[o] = e.window[o];
17
+ globalThis[a] = e.window[a];
18
18
  } catch {
19
19
  }
20
20
  const t = globalThis;
21
- t.requestAnimationFrame ??= (o) => setTimeout(() => o(Date.now()), 16), t.cancelAnimationFrame ??= (o) => clearTimeout(o), t.addEventListener ??= () => {
21
+ t.requestAnimationFrame ??= (a) => setTimeout(() => a(Date.now()), 16), t.cancelAnimationFrame ??= (a) => clearTimeout(a), t.addEventListener ??= () => {
22
22
  }, t.removeEventListener ??= () => {
23
23
  };
24
24
  const r = await import("webgpu"), d = r.create([]);
25
- for (const [o, n] of Object.entries(r.globals ?? {}))
26
- if (globalThis[o] === void 0)
25
+ for (const [a, i] of Object.entries(r.globals ?? {}))
26
+ if (globalThis[a] === void 0)
27
27
  try {
28
- globalThis[o] = n;
28
+ globalThis[a] = i;
29
29
  } catch {
30
30
  }
31
31
  if (Object.defineProperty(globalThis.navigator, "gpu", { value: d, configurable: !0 }), !await d.requestAdapter())
32
32
  throw new Error(
33
33
  "No WebGPU adapter — Route B needs a GPU or a software Vulkan driver (install Mesa lavapipe: `apt install mesa-vulkan-drivers`)."
34
34
  );
35
- const s = r.globals, f = s?.GPURenderPassEncoder?.prototype;
36
- if (f?.setBindGroup) {
37
- const o = f.setBindGroup;
38
- f.setBindGroup = function(n, ...i) {
39
- return o.call(this, Number(n), ...i);
35
+ const n = r.globals, u = n?.GPURenderPassEncoder?.prototype;
36
+ if (u?.setBindGroup) {
37
+ const a = u.setBindGroup;
38
+ u.setBindGroup = function(i, ...s) {
39
+ return a.call(this, Number(i), ...s);
40
40
  };
41
41
  }
42
- const l = s?.GPUQueue?.prototype;
42
+ const l = n?.GPUQueue?.prototype;
43
43
  if (l?.copyExternalImageToTexture && l.writeTexture) {
44
- const o = l.copyExternalImageToTexture;
45
- l.copyExternalImageToTexture = function(...n) {
46
- const i = n[0], g = n[1], m = i?.source;
47
- if (m && m[L]) {
48
- const y = m.width, x = m.height, S = m.getContext("2d").getImageData(0, 0, y, x).data, j = String(g.texture?.format ?? "").startsWith("bgra"), _ = g.premultipliedAlpha === !0, A = new Uint8Array(y * x * 4);
49
- for (let k = 0; k < y * x; k++) {
50
- const v = k * 4;
51
- let R = S[v], U = S[v + 1], E = S[v + 2];
52
- const G = S[v + 3];
53
- _ && (R = (R * G + 127) / 255 | 0, U = (U * G + 127) / 255 | 0, E = (E * G + 127) / 255 | 0), j ? (A[v] = E, A[v + 1] = U, A[v + 2] = R, A[v + 3] = G) : (A[v] = R, A[v + 1] = U, A[v + 2] = E, A[v + 3] = G);
44
+ const a = l.copyExternalImageToTexture;
45
+ l.copyExternalImageToTexture = function(...i) {
46
+ const s = i[0], f = i[1], w = s?.source;
47
+ if (w && w[k]) {
48
+ const y = w.width, x = w.height, G = w.getContext("2d").getImageData(0, 0, y, x).data, j = String(f.texture?.format ?? "").startsWith("bgra"), _ = f.premultipliedAlpha === !0, C = new Uint8Array(y * x * 4);
49
+ for (let L = 0; L < y * x; L++) {
50
+ const v = L * 4;
51
+ let S = G[v], R = G[v + 1], U = G[v + 2];
52
+ const E = G[v + 3];
53
+ _ && (S = (S * E + 127) / 255 | 0, R = (R * E + 127) / 255 | 0, U = (U * E + 127) / 255 | 0), j ? (C[v] = U, C[v + 1] = R, C[v + 2] = S, C[v + 3] = E) : (C[v] = S, C[v + 1] = R, C[v + 2] = U, C[v + 3] = E);
54
54
  }
55
55
  return this.writeTexture(
56
- { texture: g.texture, mipLevel: g.mipLevel ?? 0, origin: g.origin ?? {}, aspect: g.aspect ?? "all" },
57
- A,
56
+ { texture: f.texture, mipLevel: f.mipLevel ?? 0, origin: f.origin ?? {}, aspect: f.aspect ?? "all" },
57
+ C,
58
58
  { offset: 0, bytesPerRow: y * 4, rowsPerImage: x },
59
59
  { width: y, height: x, depthOrArrayLayers: 1 }
60
60
  );
61
61
  }
62
- return o.apply(this, n);
62
+ return a.apply(this, i);
63
63
  };
64
64
  }
65
- const { createCanvas: h, loadImage: p } = await import("@napi-rs/canvas"), w = (o, n) => {
66
- const i = h(o || 1, n || 1);
67
- i[L] = !0;
68
- const g = i.getContext.bind(i);
69
- return i.getContext = (m, ...y) => m === "2d" ? g("2d", ...y) : null, i.addEventListener = () => {
70
- }, i.removeEventListener = () => {
71
- }, i.style = {}, i.getBoundingClientRect = () => ({
65
+ const { createCanvas: p, loadImage: h } = await import("@napi-rs/canvas"), g = (a, i) => {
66
+ const s = p(a || 1, i || 1);
67
+ s[k] = !0;
68
+ const f = s.getContext.bind(s);
69
+ return s.getContext = (w, ...y) => w === "2d" ? f("2d", ...y) : null, s.addEventListener = () => {
70
+ }, s.removeEventListener = () => {
71
+ }, s.style = {}, s.getBoundingClientRect = () => ({
72
72
  x: 0,
73
73
  y: 0,
74
74
  top: 0,
75
75
  left: 0,
76
- right: i.width,
77
- bottom: i.height,
78
- width: i.width,
79
- height: i.height
80
- }), i;
76
+ right: s.width,
77
+ bottom: s.height,
78
+ width: s.width,
79
+ height: s.height
80
+ }), s;
81
81
  }, b = globalThis;
82
- b.createImageBitmap === void 0 && (b.createImageBitmap = async (o) => {
83
- const n = o;
84
- if (!n?.arrayBuffer) return o;
85
- const { Buffer: i } = await import("node:buffer"), g = await p(i.from(await n.arrayBuffer())), m = w(g.width, g.height);
86
- return m.getContext("2d").drawImage(g, 0, 0), m;
82
+ b.createImageBitmap === void 0 && (b.createImageBitmap = async (a) => {
83
+ const i = a;
84
+ if (!i?.arrayBuffer) return a;
85
+ const { Buffer: s } = await import("node:buffer"), f = await h(s.from(await i.arrayBuffer())), w = g(f.width, f.height);
86
+ return w.getContext("2d").drawImage(f, 0, 0), w;
87
87
  });
88
88
  const B = await import("pixi.js"), T = globalThis.DOMParser;
89
89
  B.DOMAdapter.set({
90
- createCanvas: (o, n) => w(o, n),
91
- createImage: () => w(1, 1),
92
- getCanvasRenderingContext2D: () => h(1, 1).getContext("2d").constructor,
90
+ createCanvas: (a, i) => g(a, i),
91
+ createImage: () => g(1, 1),
92
+ getCanvasRenderingContext2D: () => p(1, 1).getContext("2d").constructor,
93
93
  getWebGLRenderingContext: () => function() {
94
94
  },
95
95
  getNavigator: () => globalThis.navigator,
96
96
  getBaseUrl: () => "http://localhost/",
97
97
  getFontFaceSet: () => {
98
98
  },
99
- fetch: (...o) => fetch(...o),
100
- parseXML: (o) => new T().parseFromString(o, "text/xml")
99
+ fetch: (...a) => fetch(...a),
100
+ parseXML: (a) => new T().parseFromString(a, "text/xml")
101
101
  });
102
- const C = B.CanvasSource.test.bind(B.CanvasSource);
103
- B.CanvasSource.test = ((o) => !!(o && o[L]) || C(o));
104
- const u = J(import.meta.url), { registerMediabunnyServer: F } = u("@mediabunny/server");
105
- F(), I = u("mediabunny");
106
- const { setMediabunnyModule: O, setFrameImageExtractor: P } = await import("@sequio/engine");
107
- O(I), P(async (o) => {
108
- const n = o, i = n.codedWidth, g = n.codedHeight, m = new Uint8Array(n.allocationSize({ format: "RGBA" }));
109
- await n.copyTo(m, { format: "RGBA" });
110
- const y = w(i, g), x = y.getContext("2d"), D = x.createImageData(i, g);
111
- return D.data.set(m), x.putImageData(D, 0, 0), y;
102
+ const A = B.CanvasSource.test.bind(B.CanvasSource);
103
+ B.CanvasSource.test = ((a) => !!(a && a[k]) || A(a));
104
+ const m = X(import.meta.url), { registerMediabunnyServer: M } = m("@mediabunny/server");
105
+ M(), O = m("mediabunny");
106
+ const { setMediabunnyModule: D, setFrameImageExtractor: F } = await import("@sequio/engine");
107
+ D(O), F(async (a) => {
108
+ const i = a, s = i.codedWidth, f = i.codedHeight, w = new Uint8Array(i.allocationSize({ format: "RGBA" }));
109
+ await i.copyTo(w, { format: "RGBA" });
110
+ const y = g(s, f), x = y.getContext("2d"), I = x.createImageData(s, f);
111
+ return I.data.set(w), x.putImageData(I, 0, 0), y;
112
112
  });
113
- const M = await import("node-web-audio-api");
114
- for (const o of ["OfflineAudioContext", "AudioContext", "AudioBuffer"])
115
- globalThis[o] === void 0 && M[o] && (globalThis[o] = M[o]);
116
- W = !0;
113
+ const P = await import("node-web-audio-api");
114
+ for (const a of ["OfflineAudioContext", "AudioContext", "AudioBuffer"])
115
+ globalThis[a] === void 0 && P[a] && (globalThis[a] = P[a]);
116
+ V = !0;
117
117
  }
118
- async function V(a) {
118
+ async function H(o) {
119
119
  const { WebGPURenderer: e } = await import("pixi.js"), t = new e();
120
120
  return await t.init({
121
- width: a.width,
122
- height: a.height,
123
- background: a.background,
121
+ width: o.width,
122
+ height: o.height,
123
+ background: o.background,
124
124
  backgroundAlpha: 1,
125
125
  // MSAA for shape/text edges. Export renders into antialiased RenderTextures
126
126
  // (RT-level MSAA is what actually smooths the pixels read back), but honor the
127
127
  // compositor's choice here too so the renderer and its targets agree.
128
- antialias: a.antialias ?? !0,
128
+ antialias: o.antialias ?? !0,
129
129
  // Honor the compositor's resolution. Text and other Canvas-rasterized content
130
130
  // (PIXI.Text glyph atlases) are generated at the *renderer's* resolution, so
131
131
  // dropping it here rasterizes glyphs at 1× and upscales them into a higher-res
132
132
  // frame — blurry text at `--scale 2`. Match the render-texture resolution.
133
- resolution: a.resolution ?? 1
133
+ resolution: o.resolution ?? 1
134
134
  // No canvas presentation — we render to RenderTextures and read them back.
135
135
  }), t;
136
136
  }
137
- const Z = (a) => a === "vp8" || a === "vp9" || a === "av1";
138
- function ee(a) {
137
+ const ee = (o) => o === "vp8" || o === "vp9" || o === "av1";
138
+ function te(o) {
139
139
  const e = [];
140
- a.codec ? e.push({ container: a.container ?? (Z(a.codec) ? "webm" : "mp4"), codec: a.codec }) : a.container === "webm" && e.push({ container: "webm", codec: "vp9" }), e.push(
140
+ o.codec ? e.push({ container: o.container ?? (ee(o.codec) ? "webm" : "mp4"), codec: o.codec }) : o.container === "webm" && e.push({ container: "webm", codec: "vp9" }), e.push(
141
141
  { container: "mp4", codec: "avc" },
142
142
  { container: "webm", codec: "vp9" },
143
143
  { container: "webm", codec: "vp8" },
@@ -146,27 +146,27 @@ function ee(a) {
146
146
  const t = /* @__PURE__ */ new Set();
147
147
  return e.filter((r) => t.has(r.codec) ? !1 : (t.add(r.codec), !0));
148
148
  }
149
- async function z(a, e) {
150
- const t = a.gpu.device, r = a.texture.getGpuSource(e), d = e.pixelWidth, c = e.pixelHeight, s = globalThis, f = Math.ceil(d * 4 / 256) * 256, l = t.createBuffer({ size: f * c, usage: s.GPUBufferUsage.COPY_DST | s.GPUBufferUsage.MAP_READ }), h = t.createCommandEncoder();
151
- h.copyTextureToBuffer(
149
+ async function z(o, e) {
150
+ const t = o.gpu.device, r = o.texture.getGpuSource(e), d = e.pixelWidth, c = e.pixelHeight, n = globalThis, u = Math.ceil(d * 4 / 256) * 256, l = t.createBuffer({ size: u * c, usage: n.GPUBufferUsage.COPY_DST | n.GPUBufferUsage.MAP_READ }), p = t.createCommandEncoder();
151
+ p.copyTextureToBuffer(
152
152
  { texture: r },
153
- { buffer: l, bytesPerRow: f, rowsPerImage: c },
153
+ { buffer: l, bytesPerRow: u, rowsPerImage: c },
154
154
  { width: d, height: c, depthOrArrayLayers: 1 }
155
- ), t.queue.submit([h.finish()]), await l.mapAsync(s.GPUMapMode.READ);
156
- const p = new Uint8Array(l.getMappedRange()), w = new Uint8Array(d * c * 4);
155
+ ), t.queue.submit([p.finish()]), await l.mapAsync(n.GPUMapMode.READ);
156
+ const h = new Uint8Array(l.getMappedRange()), g = new Uint8Array(d * c * 4);
157
157
  for (let b = 0; b < c; b++) {
158
- const B = b * f;
158
+ const B = b * u;
159
159
  for (let T = 0; T < d; T++) {
160
- const C = B + T * 4, u = (b * d + T) * 4;
161
- w[u] = p[C + 2], w[u + 1] = p[C + 1], w[u + 2] = p[C], w[u + 3] = p[C + 3];
160
+ const A = B + T * 4, m = (b * d + T) * 4;
161
+ g[m] = h[A + 2], g[m + 1] = h[A + 1], g[m + 2] = h[A], g[m + 3] = h[A + 3];
162
162
  }
163
163
  }
164
- return l.unmap(), l.destroy(), w;
164
+ return l.unmap(), l.destroy(), g;
165
165
  }
166
- async function te(a, e, t) {
166
+ async function oe(o, e, t) {
167
167
  const r = e;
168
- await a.prepare(t);
169
- const d = a.renderToTexture(t);
168
+ await o.prepare(t);
169
+ const d = o.renderToTexture(t);
170
170
  try {
171
171
  const c = d.source;
172
172
  return { rgba: await z(r, c), width: c.pixelWidth, height: c.pixelHeight };
@@ -174,106 +174,106 @@ async function te(a, e, t) {
174
174
  d.destroy(!0);
175
175
  }
176
176
  }
177
- function oe(a) {
178
- return /VideoFrame is not defined|not supported by this browser|VideoEncoder is not|Invalid (video|audio) codec|Unknown codec/.test(String(a));
177
+ function ae(o) {
178
+ return /VideoFrame is not defined|not supported by this browser|VideoEncoder is not|Invalid (video|audio) codec|Unknown codec/.test(String(o));
179
179
  }
180
- async function ae(a, e, t) {
181
- const { Output: r, Mp4OutputFormat: d, WebMOutputFormat: c, FilePathTarget: s, VideoSampleSource: f, VideoSample: l, AudioBufferSource: h } = K(), p = await import("node:fs"), w = await import("node:path"), b = Q(t.range, t.fps), B = e, T = ee({ container: t.container, codec: t.videoCodec });
182
- let C = null;
183
- for (const u of T) {
184
- const F = w.format({ ...w.parse(t.out), base: void 0, ext: `.${u.container}` }), O = u.container === "webm" ? new c() : new d({ fastStart: "in-memory" }), P = new r({ format: O, target: new s(F) });
185
- let M = null;
180
+ async function re(o, e, t) {
181
+ const { Output: r, Mp4OutputFormat: d, WebMOutputFormat: c, FilePathTarget: n, VideoSampleSource: u, VideoSample: l, AudioBufferSource: p } = Z(), h = await import("node:fs"), g = await import("node:path"), b = J(t.range, t.fps), B = e, T = te({ container: t.container, codec: t.videoCodec });
182
+ let A = null;
183
+ for (const m of T) {
184
+ const M = g.format({ ...g.parse(t.out), base: void 0, ext: `.${m.container}` }), D = m.container === "webm" ? new c() : new d({ fastStart: "in-memory" }), F = new r({ format: D, target: new n(M) });
185
+ let P = null;
186
186
  try {
187
- const o = new f({ codec: u.codec, bitrate: t.bitrate ?? 5e6 });
188
- if (P.addVideoTrack(o), t.audio) {
189
- const n = u.container === "webm" ? "opus" : t.audio.codec ?? "aac";
190
- M = new h({ codec: n, bitrate: t.audio.bitrate ?? 128e3 }), P.addAudioTrack(M);
187
+ const a = new u({ codec: m.codec, bitrate: t.bitrate ?? 5e6 });
188
+ if (F.addVideoTrack(a), t.audio) {
189
+ const i = m.container === "webm" ? "opus" : t.audio.codec ?? "aac";
190
+ P = new p({ codec: i, bitrate: t.audio.bitrate ?? 128e3 }), F.addAudioTrack(P);
191
191
  }
192
- await P.start();
193
- for (let n = 0; n < b.length; n++) {
194
- const i = b[n];
195
- await a.prepare(i);
196
- const g = a.renderToTexture(i);
192
+ await F.start();
193
+ for (let i = 0; i < b.length; i++) {
194
+ const s = b[i];
195
+ await o.prepare(s);
196
+ const f = o.renderToTexture(s);
197
197
  try {
198
- const m = g.source, y = await z(B, m), x = new l(y, { format: "RGBA", codedWidth: m.pixelWidth, codedHeight: m.pixelHeight, timestamp: i, duration: 1 / t.fps });
199
- await o.add(x), x.close();
198
+ const w = f.source, y = await z(B, w), x = new l(y, { format: "RGBA", codedWidth: w.pixelWidth, codedHeight: w.pixelHeight, timestamp: s, duration: 1 / t.fps });
199
+ await a.add(x), x.close();
200
200
  } finally {
201
- g.destroy(!0);
201
+ f.destroy(!0);
202
202
  }
203
- t.onProgress?.((n + 1) / b.length);
203
+ t.onProgress?.((i + 1) / b.length);
204
204
  }
205
- if (M && t.audio) {
206
- const n = await t.audio.engine.renderOffline(Math.max(0, t.range[1] - t.range[0]));
207
- await M.add(n);
205
+ if (P && t.audio) {
206
+ const i = await t.audio.engine.renderOffline(Math.max(0, t.range[1] - t.range[0]));
207
+ await P.add(i);
208
208
  }
209
- return await P.finalize(), { frames: b.length, bytes: p.statSync(F).size, out: F, container: u.container, videoCodec: u.codec, audio: !!M };
210
- } catch (o) {
211
- C = o, await P.cancel().catch(() => {
209
+ return await F.finalize(), { frames: b.length, bytes: h.statSync(M).size, out: M, container: m.container, videoCodec: m.codec, audio: !!P };
210
+ } catch (a) {
211
+ A = a, await F.cancel().catch(() => {
212
212
  });
213
213
  try {
214
- p.unlinkSync(F);
214
+ h.unlinkSync(M);
215
215
  } catch {
216
216
  }
217
- if (oe(o)) {
218
- console.warn(`⚠ node-av can't encode ${u.container}/${u.codec} here — trying the next codec…`);
217
+ if (ae(a)) {
218
+ console.warn(`⚠ node-av can't encode ${m.container}/${m.codec} here — trying the next codec…`);
219
219
  continue;
220
220
  }
221
- throw o;
221
+ throw a;
222
222
  }
223
223
  }
224
224
  throw new Error(
225
- "No server-side video encoder available (tried " + T.map((u) => u.codec).join(", ") + "). Route B encodes through @mediabunny/server (node-av / FFmpeg); ensure its native binary is built (`pnpm rebuild node-av`). Last error: " + String(C)
225
+ "No server-side video encoder available (tried " + T.map((m) => m.codec).join(", ") + "). Route B encodes through @mediabunny/server (node-av / FFmpeg); ensure its native binary is built (`pnpm rebuild node-av`). Last error: " + String(A)
226
226
  );
227
227
  }
228
- function re(a) {
228
+ function ne(o) {
229
229
  const e = [], t = /src:\s*url\(([^)]+)\)/g;
230
230
  let r;
231
- for (; r = t.exec(a); )
231
+ for (; r = t.exec(o); )
232
232
  e.push(r[1].replace(/["']/g, "").trim());
233
233
  return e;
234
234
  }
235
- const ne = "Mozilla/5.0";
236
- async function N(a) {
237
- if (!a?.length) return;
235
+ const ie = "Mozilla/5.0";
236
+ async function $(o) {
237
+ if (!o?.length) return;
238
238
  const { GlobalFonts: e } = await import("@napi-rs/canvas"), { Buffer: t } = await import("node:buffer");
239
- for (const r of a)
239
+ for (const r of o)
240
240
  if (r.google) {
241
241
  const d = Y(r.google.cssBase ?? "https://fonts.googleapis.com/css2", {
242
242
  family: r.family,
243
243
  weights: r.google.weights,
244
244
  italic: r.google.italic
245
- }), c = await (await fetch(d, { headers: { "User-Agent": ne } })).text();
246
- for (const s of re(c)) {
247
- const f = t.from(await (await fetch(s)).arrayBuffer());
248
- e.register(f, r.family);
245
+ }), c = await (await fetch(d, { headers: { "User-Agent": ie } })).text();
246
+ for (const n of ne(c)) {
247
+ const u = t.from(await (await fetch(n)).arrayBuffer());
248
+ e.register(u, r.family);
249
249
  }
250
250
  } else if (r.src) {
251
251
  const d = t.from(await (await fetch(r.src)).arrayBuffer());
252
252
  e.register(d, r.family);
253
253
  }
254
254
  }
255
- function $() {
256
- const a = X.prototype;
257
- a.loadFace = (e) => {
255
+ function q() {
256
+ const o = Q.prototype;
257
+ o.loadFace = (e) => {
258
258
  const t = /^\s*url\(\s*['"]?([^'")]+)['"]?\s*\)\s*$/.exec(e.src)?.[1] ?? (/^\s*(url|local)\s*\(/.test(e.src) ? null : e.src);
259
- return t ? N([{ family: e.family, src: t }]) : Promise.resolve();
260
- }, a.loadGoogle = (e) => N([{ family: e.family, google: { weights: e.weights, italic: e.italic, cssBase: e.cssBase } }]);
259
+ return t ? $([{ family: e.family, src: t }]) : Promise.resolve();
260
+ }, o.loadGoogle = (e) => $([{ family: e.family, google: { weights: e.weights, italic: e.italic, cssBase: e.cssBase } }]);
261
261
  }
262
- async function ge(a, e) {
263
- await q(), $();
262
+ async function pe(o, e) {
263
+ await N(), q();
264
264
  const t = Math.max(1, e.scale ?? 1);
265
265
  let r = null;
266
- const c = await (await new H({ ...a, externals: e.externals, loadAsset: e.loadAsset }).run()).build({
266
+ const c = await (await new W({ ...o, externals: e.externals, loadAsset: e.loadAsset }).run()).build({
267
267
  target: "server",
268
268
  compositorOptions: {
269
- createRenderer: async (s) => (r = await V(s), r),
269
+ createRenderer: async (n) => (r = await H(n), r),
270
270
  resolution: t
271
271
  }
272
272
  });
273
273
  try {
274
- const s = c.compositor.timebase.fps;
275
- return await ae(c.compositor, r, {
276
- fps: s,
274
+ const n = c.compositor.timebase.fps;
275
+ return await re(c.compositor, r, {
276
+ fps: n,
277
277
  range: [0, c.duration],
278
278
  out: e.out,
279
279
  container: e.container,
@@ -285,42 +285,75 @@ async function ge(a, e) {
285
285
  c.dispose();
286
286
  }
287
287
  }
288
- async function ie(a, e, t, r) {
289
- const { createCanvas: d } = await import("@napi-rs/canvas"), c = await import("node:fs"), s = d(t, r), f = s.getContext("2d"), l = f.createImageData(t, r);
290
- l.data.set(e), f.putImageData(l, 0, 0);
291
- const h = await s.encode("png");
292
- return c.writeFileSync(a, h), h.length;
288
+ async function ce(o, e, t, r) {
289
+ const { createCanvas: d } = await import("@napi-rs/canvas"), c = await import("node:fs"), n = d(t, r), u = n.getContext("2d"), l = u.createImageData(t, r);
290
+ l.data.set(e), u.putImageData(l, 0, 0);
291
+ const p = await n.encode("png");
292
+ return c.writeFileSync(o, p), p.length;
293
293
  }
294
- function ce(a) {
295
- return a.replace(/\.[^./\\]*$/, "") + ".png";
294
+ function se(o) {
295
+ return o.replace(/\.[^./\\]*$/, "") + ".png";
296
296
  }
297
- async function me(a, e) {
298
- await q(), $();
297
+ async function he(o, e) {
298
+ await N(), q();
299
299
  const t = Math.max(1, e.scale ?? 1);
300
300
  let r = null;
301
- const c = await (await new H({ ...a, externals: e.externals, loadAsset: e.loadAsset }).run()).build({
301
+ const c = await (await new W({ ...o, externals: e.externals, loadAsset: e.loadAsset }).run()).build({
302
302
  target: "server",
303
303
  compositorOptions: {
304
- createRenderer: async (s) => (r = await V(s), r),
304
+ createRenderer: async (n) => (r = await H(n), r),
305
305
  resolution: t
306
306
  }
307
307
  });
308
308
  try {
309
- const s = Math.min(Math.max(e.time ?? 0, 0), c.duration), { rgba: f, width: l, height: h } = await te(c.compositor, r, s), p = ce(e.out), w = await ie(p, f, l, h);
310
- return { out: p, time: s, width: l, height: h, bytes: w };
309
+ const n = Math.min(Math.max(e.time ?? 0, 0), c.duration), { rgba: u, width: l, height: p } = await oe(c.compositor, r, n), h = se(e.out), g = await ce(h, u, l, p);
310
+ return { out: h, time: n, width: l, height: p, bytes: g };
311
311
  } finally {
312
312
  c.dispose();
313
313
  }
314
314
  }
315
+ const de = { m4a: "m4a", mp3: "mp3", wav: "wav", ogg: "ogg", webm: "webm" }, le = {
316
+ m4a: "aac",
317
+ mp3: "mp3",
318
+ wav: "pcm-s16",
319
+ ogg: "opus",
320
+ webm: "opus"
321
+ };
322
+ function ue(o, e) {
323
+ return o.replace(/\.[^./\\]*$/, "") + "." + de[e];
324
+ }
325
+ async function be(o, e) {
326
+ await N(), q();
327
+ const t = e.format ?? "mp3", r = e.codec ?? le[t];
328
+ let d = null;
329
+ const n = await (await new W({ ...o, externals: e.externals, loadAsset: e.loadAsset }).run()).build({
330
+ target: "server",
331
+ compositorOptions: {
332
+ createRenderer: async (u) => (d = await H(u), d)
333
+ }
334
+ });
335
+ try {
336
+ const l = await new K(n.compositor, n.audioEngine).exportAudio({
337
+ format: t,
338
+ codec: e.codec,
339
+ bitrate: e.bitrate,
340
+ range: [0, n.duration]
341
+ }), p = await import("node:fs"), h = ue(e.out, t), g = Buffer.from(await l.arrayBuffer());
342
+ return p.writeFileSync(h, g), { out: h, format: t, codec: r, duration: n.duration, bytes: g.length };
343
+ } finally {
344
+ n.dispose();
345
+ }
346
+ }
315
347
  export {
316
- $ as bridgeFontManagerToNode,
317
- V as createNodeWebGPURenderer,
318
- K as getMediabunny,
319
- N as loadFontsNode,
320
- me as renderBundleFrameToFile,
321
- ge as renderBundleToFile,
322
- te as renderFrameRGBA,
323
- ae as renderTimelineToFile,
324
- q as setupNodeEnvironment
348
+ q as bridgeFontManagerToNode,
349
+ H as createNodeWebGPURenderer,
350
+ be as exportBundleAudioToFile,
351
+ Z as getMediabunny,
352
+ $ as loadFontsNode,
353
+ he as renderBundleFrameToFile,
354
+ pe as renderBundleToFile,
355
+ oe as renderFrameRGBA,
356
+ re as renderTimelineToFile,
357
+ N as setupNodeEnvironment
325
358
  };
326
359
  //# sourceMappingURL=route-b.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequio/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Server-side rendering for sequio: a serializable TimelineSpec protocol plus Route B — pure Node, PixiJS WebGPU. (Route A, headless Chrome, is a repo-internal harness and not part of the published package.)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,8 +48,8 @@
48
48
  "dependencies": {
49
49
  "mediabunny": "^1.49.0",
50
50
  "pixi.js": "^8.6.0",
51
- "@sequio/engine": "^0.1.1",
52
- "@sequio/runtime": "^0.1.0"
51
+ "@sequio/engine": "^0.1.2",
52
+ "@sequio/runtime": "^0.1.2"
53
53
  },
54
54
  "optionalDependencies": {
55
55
  "@mediabunny/server": "^1.50.4",