@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.
- package/dist/route-b/audio-node.d.ts +31 -0
- package/dist/route-b/index.d.ts +1 -0
- package/dist/route-b.js +205 -172
- package/package.json +3 -3
|
@@ -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
|
package/dist/route-b/index.d.ts
CHANGED
|
@@ -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
|
|
2
|
-
import { exportFrameTimes as
|
|
3
|
-
import { Runtime as
|
|
4
|
-
let
|
|
5
|
-
const
|
|
6
|
-
let
|
|
7
|
-
function
|
|
8
|
-
if (!
|
|
9
|
-
return
|
|
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
|
|
12
|
-
if (
|
|
13
|
-
const { JSDOM:
|
|
14
|
-
for (const
|
|
15
|
-
if (globalThis[
|
|
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[
|
|
17
|
+
globalThis[a] = e.window[a];
|
|
18
18
|
} catch {
|
|
19
19
|
}
|
|
20
20
|
const t = globalThis;
|
|
21
|
-
t.requestAnimationFrame ??= (
|
|
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 [
|
|
26
|
-
if (globalThis[
|
|
25
|
+
for (const [a, i] of Object.entries(r.globals ?? {}))
|
|
26
|
+
if (globalThis[a] === void 0)
|
|
27
27
|
try {
|
|
28
|
-
globalThis[
|
|
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
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return
|
|
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 =
|
|
42
|
+
const l = n?.GPUQueue?.prototype;
|
|
43
43
|
if (l?.copyExternalImageToTexture && l.writeTexture) {
|
|
44
|
-
const
|
|
45
|
-
l.copyExternalImageToTexture = function(...
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
const y =
|
|
49
|
-
for (let
|
|
50
|
-
const v =
|
|
51
|
-
let
|
|
52
|
-
const
|
|
53
|
-
_ && (
|
|
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:
|
|
57
|
-
|
|
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
|
|
62
|
+
return a.apply(this, i);
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
const { createCanvas:
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
return
|
|
70
|
-
},
|
|
71
|
-
},
|
|
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:
|
|
77
|
-
bottom:
|
|
78
|
-
width:
|
|
79
|
-
height:
|
|
80
|
-
}),
|
|
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 (
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
const { Buffer:
|
|
86
|
-
return
|
|
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: (
|
|
91
|
-
createImage: () =>
|
|
92
|
-
getCanvasRenderingContext2D: () =>
|
|
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: (...
|
|
100
|
-
parseXML: (
|
|
99
|
+
fetch: (...a) => fetch(...a),
|
|
100
|
+
parseXML: (a) => new T().parseFromString(a, "text/xml")
|
|
101
101
|
});
|
|
102
|
-
const
|
|
103
|
-
B.CanvasSource.test = ((
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const { setMediabunnyModule:
|
|
107
|
-
O
|
|
108
|
-
const
|
|
109
|
-
await
|
|
110
|
-
const y =
|
|
111
|
-
return
|
|
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
|
|
114
|
-
for (const
|
|
115
|
-
globalThis[
|
|
116
|
-
|
|
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
|
|
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:
|
|
122
|
-
height:
|
|
123
|
-
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:
|
|
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:
|
|
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
|
|
138
|
-
function
|
|
137
|
+
const ee = (o) => o === "vp8" || o === "vp9" || o === "av1";
|
|
138
|
+
function te(o) {
|
|
139
139
|
const e = [];
|
|
140
|
-
|
|
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(
|
|
150
|
-
const t =
|
|
151
|
-
|
|
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:
|
|
153
|
+
{ buffer: l, bytesPerRow: u, rowsPerImage: c },
|
|
154
154
|
{ width: d, height: c, depthOrArrayLayers: 1 }
|
|
155
|
-
), t.queue.submit([
|
|
156
|
-
const
|
|
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 *
|
|
158
|
+
const B = b * u;
|
|
159
159
|
for (let T = 0; T < d; T++) {
|
|
160
|
-
const
|
|
161
|
-
|
|
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(),
|
|
164
|
+
return l.unmap(), l.destroy(), g;
|
|
165
165
|
}
|
|
166
|
-
async function
|
|
166
|
+
async function oe(o, e, t) {
|
|
167
167
|
const r = e;
|
|
168
|
-
await
|
|
169
|
-
const d =
|
|
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
|
|
178
|
-
return /VideoFrame is not defined|not supported by this browser|VideoEncoder is not|Invalid (video|audio) codec|Unknown codec/.test(String(
|
|
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
|
|
181
|
-
const { Output: r, Mp4OutputFormat: d, WebMOutputFormat: c, FilePathTarget:
|
|
182
|
-
let
|
|
183
|
-
for (const
|
|
184
|
-
const
|
|
185
|
-
let
|
|
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
|
|
188
|
-
if (
|
|
189
|
-
const
|
|
190
|
-
|
|
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
|
|
193
|
-
for (let
|
|
194
|
-
const
|
|
195
|
-
await
|
|
196
|
-
const
|
|
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
|
|
199
|
-
await
|
|
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
|
-
|
|
201
|
+
f.destroy(!0);
|
|
202
202
|
}
|
|
203
|
-
t.onProgress?.((
|
|
203
|
+
t.onProgress?.((i + 1) / b.length);
|
|
204
204
|
}
|
|
205
|
-
if (
|
|
206
|
-
const
|
|
207
|
-
await
|
|
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
|
|
210
|
-
} catch (
|
|
211
|
-
|
|
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
|
-
|
|
214
|
+
h.unlinkSync(M);
|
|
215
215
|
} catch {
|
|
216
216
|
}
|
|
217
|
-
if (
|
|
218
|
-
console.warn(`⚠ node-av can't encode ${
|
|
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
|
|
221
|
+
throw a;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
throw new Error(
|
|
225
|
-
"No server-side video encoder available (tried " + T.map((
|
|
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
|
|
228
|
+
function ne(o) {
|
|
229
229
|
const e = [], t = /src:\s*url\(([^)]+)\)/g;
|
|
230
230
|
let r;
|
|
231
|
-
for (; r = t.exec(
|
|
231
|
+
for (; r = t.exec(o); )
|
|
232
232
|
e.push(r[1].replace(/["']/g, "").trim());
|
|
233
233
|
return e;
|
|
234
234
|
}
|
|
235
|
-
const
|
|
236
|
-
async function
|
|
237
|
-
if (!
|
|
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
|
|
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":
|
|
246
|
-
for (const
|
|
247
|
-
const
|
|
248
|
-
e.register(
|
|
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
|
|
257
|
-
|
|
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 ?
|
|
260
|
-
},
|
|
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
|
|
263
|
-
await
|
|
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
|
|
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 (
|
|
269
|
+
createRenderer: async (n) => (r = await H(n), r),
|
|
270
270
|
resolution: t
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
273
|
try {
|
|
274
|
-
const
|
|
275
|
-
return await
|
|
276
|
-
fps:
|
|
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
|
|
289
|
-
const { createCanvas: d } = await import("@napi-rs/canvas"), c = await import("node:fs"),
|
|
290
|
-
l.data.set(e),
|
|
291
|
-
const
|
|
292
|
-
return c.writeFileSync(
|
|
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
|
|
295
|
-
return
|
|
294
|
+
function se(o) {
|
|
295
|
+
return o.replace(/\.[^./\\]*$/, "") + ".png";
|
|
296
296
|
}
|
|
297
|
-
async function
|
|
298
|
-
await
|
|
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
|
|
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 (
|
|
304
|
+
createRenderer: async (n) => (r = await H(n), r),
|
|
305
305
|
resolution: t
|
|
306
306
|
}
|
|
307
307
|
});
|
|
308
308
|
try {
|
|
309
|
-
const
|
|
310
|
-
return { out:
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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.
|
|
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.
|
|
52
|
-
"@sequio/runtime": "^0.1.
|
|
51
|
+
"@sequio/engine": "^0.1.2",
|
|
52
|
+
"@sequio/runtime": "^0.1.2"
|
|
53
53
|
},
|
|
54
54
|
"optionalDependencies": {
|
|
55
55
|
"@mediabunny/server": "^1.50.4",
|