@sequio/cli 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/args.d.ts CHANGED
@@ -27,6 +27,21 @@ export interface PreviewCommand {
27
27
  /** Bind to `0.0.0.0` (expose on the network) instead of localhost. */
28
28
  host: boolean;
29
29
  }
30
+ /** Audio-only container formats `sequio audio` can write. */
31
+ export declare const AUDIO_FORMATS: readonly ["m4a", "mp3", "wav", "ogg", "webm"];
32
+ export type AudioFormat = (typeof AUDIO_FORMATS)[number];
33
+ /** `sequio audio <file> [-o out.m4a] [--format m4a] [--bitrate N]`. */
34
+ export interface AudioCommand {
35
+ kind: 'audio';
36
+ /** Path to the entry composition file. */
37
+ file: string;
38
+ /** Output audio path. `undefined` → `out.<format>`. */
39
+ out?: string;
40
+ /** Audio container format. `undefined` → inferred from `out`'s extension, else `mp3`. */
41
+ format?: AudioFormat;
42
+ /** Target audio bitrate, bits/sec. `undefined` → the engine default (128 kbps). */
43
+ bitrate?: number;
44
+ }
30
45
  /** `sequio frame <file> [-t sec] [-o out.png] [--scale N]`. */
31
46
  export interface FrameCommand {
32
47
  kind: 'frame';
@@ -39,15 +54,23 @@ export interface FrameCommand {
39
54
  /** Output resolution multiplier (N× the composition size). @default 1 */
40
55
  scale: number;
41
56
  }
57
+ /** `sequio check <file> [--json]`. */
58
+ export interface CheckCommand {
59
+ kind: 'check';
60
+ /** Path to the entry composition file. */
61
+ file: string;
62
+ /** Emit machine-readable `Diagnostic[]` JSON instead of human-readable lines. */
63
+ json: boolean;
64
+ }
42
65
  /** Show usage / version / a parse error and exit. */
43
66
  export interface MetaCommand {
44
67
  kind: 'help' | 'version' | 'error';
45
68
  /** For `error`: the message to print (before usage) and exit non-zero. */
46
69
  message?: string;
47
70
  }
48
- export type CliCommand = RenderCommand | PreviewCommand | FrameCommand | MetaCommand;
71
+ export type CliCommand = RenderCommand | PreviewCommand | FrameCommand | AudioCommand | CheckCommand | MetaCommand;
49
72
  export declare const DEFAULT_PREVIEW_PORT = 6180;
50
- export declare const USAGE = "sequio \u2014 programmable-timeline CLI\n\nUsage:\n sequio render <file> [options] Encode a composition to a video file\n sequio frame <file> [options] Export a single frame at a time as a PNG\n sequio preview <file> [options] Serve a live in-browser preview\n\nRender options (pure Node, PixiJS WebGPU \u2014 needs a GPU or Mesa lavapipe):\n -o, --out <path> Output path (default: out.mp4)\n -s, --scale <n> Render at n\u00D7 the composition resolution (default: 1)\n --verify Assert a valid video container came out\n\nFrame options (pure Node, PixiJS WebGPU \u2014 needs a GPU or Mesa lavapipe):\n -t, --time <sec> Timeline time to sample, in seconds (default: 0)\n -o, --out <path> Output PNG path (default: frame.png)\n -s, --scale <n> Render at n\u00D7 the composition resolution (default: 1)\n\nPreview options:\n -w, --watch Re-run on any project-file change (live reload)\n -p, --port <n> Dev-server port (default: 6180)\n --host Expose the server on the local network (0.0.0.0)\n\nGlobal:\n -h, --help Show this help\n -v, --version Print the version\n\n<file> is a TS/JS module whose default export is a\ndefineComposition(builder) (or a bare builder). Its sibling files are\nbundled with it, so relative imports (./scene, ./title) resolve.";
73
+ export declare const USAGE: string;
51
74
  /** Parse `process.argv.slice(2)` into a {@link CliCommand}. Never throws. */
52
75
  export declare function parseArgs(argv: string[]): CliCommand;
53
76
  //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { AudioFormat } from './args';
2
+ export interface AudioOptions {
3
+ /** Output audio path. `undefined` → `out.<format>`. */
4
+ out?: string;
5
+ /** Audio container format. `undefined` → inferred from `out`'s extension, else `mp3`. */
6
+ format?: AudioFormat;
7
+ /** Target audio bitrate, bits/sec. `undefined` → the engine default (128 kbps). */
8
+ bitrate?: number;
9
+ }
10
+ /**
11
+ * Export the audio of `entryFile` to a file, returning the process exit code
12
+ * (0 = success).
13
+ */
14
+ export declare function runAudio(entryFile: string, options?: AudioOptions): Promise<number>;
15
+ //# sourceMappingURL=audio.d.ts.map
@@ -0,0 +1,46 @@
1
+ import { Renderer } from '@sequio/engine';
2
+ import { Externals, RuntimeBundle } from '@sequio/runtime';
3
+ /** A single validation finding. */
4
+ export interface Diagnostic {
5
+ /** `error` fails the check (non-zero exit); `warn` is advisory. */
6
+ severity: 'error' | 'warn';
7
+ /** Stable code (`A1`…`C9`) identifying the check that fired. */
8
+ code: string;
9
+ /** Human-readable explanation. */
10
+ message: string;
11
+ /** Where in the object graph it was found (e.g. `track 0 · clip 2 (TextClip)`). */
12
+ at?: string;
13
+ }
14
+ export interface CheckBundleOptions {
15
+ /** Extra bare modules the composition may import (e.g. `{ gsap }`). */
16
+ externals?: Externals;
17
+ /**
18
+ * Existence predicate for `loadAsset('./x.mp4')` paths (normalized,
19
+ * root-relative). Returns `false` → a C7 "asset not found" diagnostic. Omit to
20
+ * skip asset-existence checking (in-memory tests have no disk).
21
+ */
22
+ assetExists?: (path: string) => boolean;
23
+ }
24
+ /**
25
+ * A headless no-op {@link Renderer}: `init()` resolves without a GPU and nothing
26
+ * ever draws (check reconciles no frames). Injected via
27
+ * `CompositorOptions.createRenderer`, so `await compositor.init()` in a builder
28
+ * completes on a machine with no Vulkan at all.
29
+ */
30
+ export declare function nullRenderer(): Promise<Renderer>;
31
+ /**
32
+ * Validate a {@link RuntimeBundle} and return every {@link Diagnostic} found.
33
+ * Pure and GPU-free — the unit-testable core behind `runCheck`.
34
+ */
35
+ export declare function checkBundle(bundle: RuntimeBundle, options?: CheckBundleOptions): Promise<Diagnostic[]>;
36
+ export interface CheckOptions {
37
+ /** Emit machine-readable `Diagnostic[]` JSON instead of human lines. */
38
+ json?: boolean;
39
+ }
40
+ /**
41
+ * Statically check `entryFile` and return the process exit code (0 = no errors,
42
+ * 1 = at least one `error`). `warn`-only results still exit 0. Prints a
43
+ * human-readable summary, or a `Diagnostic[]` JSON array with `--json`.
44
+ */
45
+ export declare function runCheck(entryFile: string, options?: CheckOptions): Promise<number>;
46
+ //# sourceMappingURL=check.d.ts.map
package/dist/cli.js CHANGED
@@ -1,34 +1,38 @@
1
- import { p as n, s as t, a as i, b as l, U as c, v as p } from "./version-B2TKvZen.js";
2
- async function u(s) {
3
- const e = n(s);
1
+ import { p as c, s as n, a as i, d as l, e as u, b as f, U as t, v as p } from "./version-C7OkFI_V.js";
2
+ async function m(r) {
3
+ const e = c(r);
4
4
  switch (e.kind) {
5
5
  case "help":
6
- return console.log(c), 0;
6
+ return console.log(t), 0;
7
7
  case "version":
8
8
  return console.log(p), 0;
9
9
  case "error":
10
10
  return console.error(`✖ ${e.message}
11
- `), console.error(c), 2;
11
+ `), console.error(t), 2;
12
+ case "check":
13
+ return f(e.file, { json: e.json });
12
14
  case "render":
13
- return l(e.file, { out: e.out, scale: e.scale, verify: e.verify });
15
+ return u(e.file, { out: e.out, scale: e.scale, verify: e.verify });
14
16
  case "frame":
15
- return i(e.file, { out: e.out, time: e.time, scale: e.scale });
17
+ return l(e.file, { out: e.out, time: e.time, scale: e.scale });
18
+ case "audio":
19
+ return i(e.file, { out: e.out, format: e.format, bitrate: e.bitrate });
16
20
  case "preview": {
17
- const r = await t(e.file, {
21
+ const s = await n(e.file, {
18
22
  port: e.port,
19
23
  host: e.host,
20
24
  watch: e.watch
21
25
  });
22
- return console.log(`▸ sequio preview → ${r.url}`), console.log(` serving ${e.file}${e.watch ? " (watching for changes)" : ""}`), console.log(" press Ctrl-C to stop."), await new Promise((a) => {
26
+ return console.log(`▸ sequio preview → ${s.url}`), console.log(` serving ${e.file}${e.watch ? " (watching for changes)" : ""}`), console.log(" press Ctrl-C to stop."), await new Promise((a) => {
23
27
  const o = () => {
24
- r.close().finally(() => a());
28
+ s.close().finally(() => a());
25
29
  };
26
30
  process.once("SIGINT", o), process.once("SIGTERM", o);
27
31
  }), 0;
28
32
  }
29
33
  }
30
34
  }
31
- u(process.argv.slice(2)).then((s) => process.exit(s)).catch((s) => {
32
- console.error("✖", s instanceof Error ? s.message : s), process.exit(1);
35
+ m(process.argv.slice(2)).then((r) => process.exit(r)).catch((r) => {
36
+ console.error("✖", r instanceof Error ? r.message : r), process.exit(1);
33
37
  });
34
38
  //# sourceMappingURL=cli.js.map
package/dist/index.d.ts CHANGED
@@ -1,22 +1,29 @@
1
1
  /**
2
2
  * `@sequio/cli` — the `sequio` command line.
3
3
  *
4
- * Three commands, all thin front-ends over infrastructure the other packages
4
+ * Five commands, all thin front-ends over infrastructure the other packages
5
5
  * already own:
6
+ * - `check <file>` → compile + link + build the object graph with a null
7
+ * renderer and statically validate it — no GPU, no network (the fast
8
+ * pre-flight in the `check → frame → render` loop).
6
9
  * - `render <file>` → snapshot the composition into a {@link RuntimeBundle} and
7
10
  * hand it to the server's Route B (pure Node, WebGPU) to encode a video.
8
11
  * - `frame <file>` → the same Route B path, but seek to one time and write a
9
12
  * single PNG — a fast visual check without a full render.
13
+ * - `audio <file>` → the same Route B path, but export only the audio mix to an
14
+ * audio-only file (m4a / mp3 / wav / ogg / webm).
10
15
  * - `preview <file>` → boot a Vite dev server whose page runs the same
11
16
  * `Runtime` → `Composer` → `preview()` path in-browser; `--watch` live-reloads.
12
17
  *
13
18
  * This barrel is the programmatic surface (used by tests and embedders); the
14
19
  * `sequio` binary is `bin/sequio.js`, which runs `src/cli.ts`.
15
20
  */
16
- export { parseArgs, USAGE, DEFAULT_PREVIEW_PORT, type CliCommand, type RenderCommand, type FrameCommand, type PreviewCommand, type MetaCommand, } from './args';
21
+ export { parseArgs, USAGE, DEFAULT_PREVIEW_PORT, AUDIO_FORMATS, type AudioFormat, type CliCommand, type RenderCommand, type FrameCommand, type AudioCommand, type CheckCommand, type PreviewCommand, type MetaCommand, } from './args';
17
22
  export { readBundle } from './bundle';
23
+ export { runCheck, checkBundle, nullRenderer, type Diagnostic, type CheckOptions, type CheckBundleOptions, } from './check';
18
24
  export { runRender, type RenderOptions } from './render';
19
25
  export { runFrame, type FrameOptions } from './frame';
26
+ export { runAudio, type AudioOptions } from './audio';
20
27
  export { startPreviewServer, type PreviewServer, type PreviewServerOptions, } from './preview';
21
28
  export { version } from './version';
22
29
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
- import { D as s, U as e, p as n, r as v, a as E, b as d, s as o, v as p } from "./version-B2TKvZen.js";
1
+ import { A as a, D as s, U as n, c as u, n as d, p as A, r as R, a as c, b as l, d as o, e as v, s as E, v as U } from "./version-C7OkFI_V.js";
2
2
  export {
3
+ a as AUDIO_FORMATS,
3
4
  s as DEFAULT_PREVIEW_PORT,
4
- e as USAGE,
5
- n as parseArgs,
6
- v as readBundle,
7
- E as runFrame,
8
- d as runRender,
9
- o as startPreviewServer,
10
- p as version
5
+ n as USAGE,
6
+ u as checkBundle,
7
+ d as nullRenderer,
8
+ A as parseArgs,
9
+ R as readBundle,
10
+ c as runAudio,
11
+ l as runCheck,
12
+ o as runFrame,
13
+ v as runRender,
14
+ E as startPreviewServer,
15
+ U as version
11
16
  };
12
17
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,760 @@
1
+ import { existsSync as $, readFileSync as A, statSync as R, createReadStream as U } from "node:fs";
2
+ import { resolve as m, dirname as h, basename as P } from "node:path";
3
+ import { Compositor as N, FontManager as B, fonts as I, VisualTrack as L, VisualClip as j, GroupClip as O, TextClip as b } from "@sequio/engine";
4
+ import { Runtime as F, ModuleResolutionError as G, resolveAssetPath as _ } from "@sequio/runtime";
5
+ import { cliExternals as w } from "./externals.js";
6
+ import { fileURLToPath as D } from "node:url";
7
+ import { NodeFileSystem as W } from "@sequio/runtime/node-fs";
8
+ import { createRequire as q } from "node:module";
9
+ const v = ["m4a", "mp3", "wav", "ogg", "webm"], x = 6180, be = `sequio — programmable-timeline CLI
10
+
11
+ Usage:
12
+ sequio check <file> [options] Statically validate a composition (no GPU, offline)
13
+ sequio render <file> [options] Encode a composition to a video file
14
+ sequio frame <file> [options] Export a single frame at a time as a PNG
15
+ sequio audio <file> [options] Export just the audio track to an audio file
16
+ sequio preview <file> [options] Serve a live in-browser preview
17
+
18
+ Check options (no GPU, no network — the fast pre-flight before frame/render):
19
+ --json Emit machine-readable Diagnostic[] JSON
20
+
21
+ Render options (pure Node, PixiJS WebGPU — needs a GPU or Mesa lavapipe):
22
+ -o, --out <path> Output path (default: out.mp4)
23
+ -s, --scale <n> Render at n× the composition resolution (default: 1)
24
+ --verify Assert a valid video container came out
25
+
26
+ Frame options (pure Node, PixiJS WebGPU — needs a GPU or Mesa lavapipe):
27
+ -t, --time <sec> Timeline time to sample, in seconds (default: 0)
28
+ -o, --out <path> Output PNG path (default: frame.png)
29
+ -s, --scale <n> Render at n× the composition resolution (default: 1)
30
+
31
+ Audio options:
32
+ -o, --out <path> Output path (default: out.<format>)
33
+ -f, --format <fmt> Container: ${v.join(" | ")} (default: mp3, or inferred from --out)
34
+ -b, --bitrate <bps> Target audio bitrate in bits/sec (default: 128000; ignored for wav)
35
+
36
+ Preview options:
37
+ -w, --watch Re-run on any project-file change (live reload)
38
+ -p, --port <n> Dev-server port (default: ${x})
39
+ --host Expose the server on the local network (0.0.0.0)
40
+
41
+ Global:
42
+ -h, --help Show this help
43
+ -v, --version Print the version
44
+
45
+ <file> is a TS/JS module whose default export is a
46
+ defineComposition(builder) (or a bare builder). Its sibling files are
47
+ bundled with it, so relative imports (./scene, ./title) resolve.`;
48
+ function ke(e) {
49
+ if (e[0] === "--" && (e = e.slice(1)), e.length === 0) return { kind: "help" };
50
+ const t = e[0];
51
+ return t === "-h" || t === "--help" ? { kind: "help" } : t === "-v" || t === "--version" ? { kind: "version" } : t === "check" ? M(e.slice(1)) : t === "render" ? H(e.slice(1)) : t === "frame" ? z(e.slice(1)) : t === "audio" ? J(e.slice(1)) : t === "preview" ? V(e.slice(1)) : { kind: "error", message: `Unknown command: ${t}` };
52
+ }
53
+ function M(e) {
54
+ let t, n = !1;
55
+ for (let c = 0; c < e.length; c++) {
56
+ const i = e[c];
57
+ if (i === "-h" || i === "--help") return { kind: "help" };
58
+ if (i === "--json") n = !0;
59
+ else {
60
+ if (i.startsWith("-")) return { kind: "error", message: `Unknown option: ${i}` };
61
+ if (t === void 0) t = i;
62
+ else return { kind: "error", message: `Unexpected argument: ${i}` };
63
+ }
64
+ }
65
+ return t === void 0 ? { kind: "error", message: "check needs a <file>" } : { kind: "check", file: t, json: n };
66
+ }
67
+ function H(e) {
68
+ let t, n, c = 1, i = !1;
69
+ for (let o = 0; o < e.length; o++) {
70
+ const r = e[o];
71
+ if (r === "-h" || r === "--help") return { kind: "help" };
72
+ if (r === "-o" || r === "--out") {
73
+ if (n = e[++o], n === void 0) return { kind: "error", message: `${r} needs a path` };
74
+ } else if (r === "-s" || r === "--scale") {
75
+ const s = e[++o], a = Number(s);
76
+ if (!(a >= 1) || !Number.isFinite(a)) return { kind: "error", message: `${r} needs a number ≥ 1, got: ${s}` };
77
+ c = a;
78
+ } else if (r === "--verify") i = !0;
79
+ else {
80
+ if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
81
+ if (t === void 0) t = r;
82
+ else return { kind: "error", message: `Unexpected argument: ${r}` };
83
+ }
84
+ }
85
+ return t === void 0 ? { kind: "error", message: "render needs a <file>" } : { kind: "render", file: t, out: n, scale: c, verify: i };
86
+ }
87
+ function z(e) {
88
+ let t, n, c = 0, i = 1;
89
+ for (let o = 0; o < e.length; o++) {
90
+ const r = e[o];
91
+ if (r === "-h" || r === "--help") return { kind: "help" };
92
+ if (r === "-o" || r === "--out") {
93
+ if (n = e[++o], n === void 0) return { kind: "error", message: `${r} needs a path` };
94
+ } else if (r === "-t" || r === "--time") {
95
+ const s = e[++o], a = Number(s);
96
+ if (!Number.isFinite(a) || a < 0) return { kind: "error", message: `${r} needs a number ≥ 0, got: ${s}` };
97
+ c = a;
98
+ } else if (r === "-s" || r === "--scale") {
99
+ const s = e[++o], a = Number(s);
100
+ if (!(a >= 1) || !Number.isFinite(a)) return { kind: "error", message: `${r} needs a number ≥ 1, got: ${s}` };
101
+ i = a;
102
+ } else {
103
+ if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
104
+ if (t === void 0) t = r;
105
+ else return { kind: "error", message: `Unexpected argument: ${r}` };
106
+ }
107
+ }
108
+ return t === void 0 ? { kind: "error", message: "frame needs a <file>" } : { kind: "frame", file: t, time: c, out: n, scale: i };
109
+ }
110
+ function J(e) {
111
+ let t, n, c, i;
112
+ for (let o = 0; o < e.length; o++) {
113
+ const r = e[o];
114
+ if (r === "-h" || r === "--help") return { kind: "help" };
115
+ if (r === "-o" || r === "--out") {
116
+ if (n = e[++o], n === void 0) return { kind: "error", message: `${r} needs a path` };
117
+ } else if (r === "-f" || r === "--format") {
118
+ const s = e[++o];
119
+ if (!v.includes(s))
120
+ return { kind: "error", message: `${r} needs one of ${v.join(", ")}, got: ${s}` };
121
+ c = s;
122
+ } else if (r === "-b" || r === "--bitrate") {
123
+ const s = e[++o], a = Number(s);
124
+ if (!Number.isFinite(a) || a <= 0) return { kind: "error", message: `${r} needs a number > 0, got: ${s}` };
125
+ i = a;
126
+ } else {
127
+ if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
128
+ if (t === void 0) t = r;
129
+ else return { kind: "error", message: `Unexpected argument: ${r}` };
130
+ }
131
+ }
132
+ return t === void 0 ? { kind: "error", message: "audio needs a <file>" } : { kind: "audio", file: t, out: n, format: c, bitrate: i };
133
+ }
134
+ function V(e) {
135
+ let t, n = !1, c = !1, i = x;
136
+ for (let o = 0; o < e.length; o++) {
137
+ const r = e[o];
138
+ if (r === "-h" || r === "--help") return { kind: "help" };
139
+ if (r === "-w" || r === "--watch") n = !0;
140
+ else if (r === "--host") c = !0;
141
+ else if (r === "-p" || r === "--port") {
142
+ const s = e[++o], a = Number(s);
143
+ if (!Number.isInteger(a) || a <= 0 || a > 65535)
144
+ return { kind: "error", message: `${r} needs a valid port, got: ${s}` };
145
+ i = a;
146
+ } else {
147
+ if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
148
+ if (t === void 0) t = r;
149
+ else return { kind: "error", message: `Unexpected argument: ${r}` };
150
+ }
151
+ }
152
+ return t === void 0 ? { kind: "error", message: "preview needs a <file>" } : { kind: "preview", file: t, watch: n, port: i, host: c };
153
+ }
154
+ const K = /* @__PURE__ */ new Set([
155
+ // images
156
+ "png",
157
+ "jpg",
158
+ "jpeg",
159
+ "gif",
160
+ "webp",
161
+ "avif",
162
+ "bmp",
163
+ "ico",
164
+ // video
165
+ "mp4",
166
+ "webm",
167
+ "mov",
168
+ "mkv",
169
+ "avi",
170
+ "m4v",
171
+ // audio
172
+ "mp3",
173
+ "wav",
174
+ "m4a",
175
+ "aac",
176
+ "ogg",
177
+ "oga",
178
+ "flac",
179
+ "opus",
180
+ // fonts
181
+ "ttf",
182
+ "otf",
183
+ "woff",
184
+ "woff2"
185
+ ]);
186
+ function C(e) {
187
+ const t = e.lastIndexOf(".");
188
+ return t < 0 ? "" : e.slice(t + 1).toLowerCase();
189
+ }
190
+ function X(e) {
191
+ return K.has(C(e));
192
+ }
193
+ function Y(e) {
194
+ return {
195
+ png: "image/png",
196
+ jpg: "image/jpeg",
197
+ jpeg: "image/jpeg",
198
+ gif: "image/gif",
199
+ webp: "image/webp",
200
+ avif: "image/avif",
201
+ bmp: "image/bmp",
202
+ ico: "image/x-icon",
203
+ mp4: "video/mp4",
204
+ webm: "video/webm",
205
+ mov: "video/quicktime",
206
+ mkv: "video/x-matroska",
207
+ avi: "video/x-msvideo",
208
+ m4v: "video/x-m4v",
209
+ mp3: "audio/mpeg",
210
+ wav: "audio/wav",
211
+ m4a: "audio/mp4",
212
+ aac: "audio/aac",
213
+ ogg: "audio/ogg",
214
+ oga: "audio/ogg",
215
+ flac: "audio/flac",
216
+ opus: "audio/opus",
217
+ ttf: "font/ttf",
218
+ otf: "font/otf",
219
+ woff: "font/woff",
220
+ woff2: "font/woff2"
221
+ }[C(e)] ?? "application/octet-stream";
222
+ }
223
+ function g(e) {
224
+ const t = m(e), n = h(t), c = "/" + P(t), i = new W(n), o = {};
225
+ for (const r of i.listFiles()) {
226
+ if (X(r)) continue;
227
+ const s = i.readFile(r);
228
+ s !== null && (o[r] = s);
229
+ }
230
+ if (o[c] === void 0)
231
+ throw new Error(`Entry file not found: ${e}`);
232
+ return { files: o, entry: c };
233
+ }
234
+ const Q = /* @__PURE__ */ new Set([
235
+ "sans-serif",
236
+ "serif",
237
+ "monospace",
238
+ "cursive",
239
+ "fantasy",
240
+ "system-ui",
241
+ "ui-sans-serif",
242
+ "ui-serif",
243
+ "ui-monospace",
244
+ "ui-rounded",
245
+ "math",
246
+ "emoji",
247
+ "inherit",
248
+ "initial",
249
+ "unset"
250
+ ]);
251
+ function Z() {
252
+ const e = () => {
253
+ };
254
+ return Promise.resolve({ render: e, resize: e, destroy: e });
255
+ }
256
+ function ee() {
257
+ const e = globalThis;
258
+ e.createImageBitmap ??= async () => ({ width: 1, height: 1, close() {
259
+ } });
260
+ class t {
261
+ }
262
+ class n {
263
+ }
264
+ class c {
265
+ }
266
+ e.CanvasRenderingContext2D ??= t, e.HTMLCanvasElement ??= n, e.HTMLImageElement ??= c;
267
+ const i = e.CanvasRenderingContext2D, o = e.HTMLCanvasElement, r = (a) => Object.assign(new i(), {
268
+ canvas: a,
269
+ font: "10px sans-serif",
270
+ textBaseline: "alphabetic",
271
+ fillStyle: "#000",
272
+ strokeStyle: "#000",
273
+ lineWidth: 1,
274
+ globalAlpha: 1,
275
+ // A monospace-ish advance is enough for split/layout math; check never paints.
276
+ measureText: (l) => {
277
+ const d = (l?.length ?? 0) * 8;
278
+ return {
279
+ width: d,
280
+ actualBoundingBoxAscent: 8,
281
+ actualBoundingBoxDescent: 2,
282
+ actualBoundingBoxLeft: 0,
283
+ actualBoundingBoxRight: d,
284
+ fontBoundingBoxAscent: 8,
285
+ fontBoundingBoxDescent: 2
286
+ };
287
+ },
288
+ getImageData: (l, d, f, u) => ({
289
+ data: new Uint8ClampedArray(Math.max(1, f) * Math.max(1, u) * 4),
290
+ width: f,
291
+ height: u
292
+ }),
293
+ createLinearGradient: () => ({ addColorStop() {
294
+ } }),
295
+ createPattern: () => null,
296
+ fillText() {
297
+ },
298
+ strokeText() {
299
+ },
300
+ clearRect() {
301
+ },
302
+ fillRect() {
303
+ },
304
+ save() {
305
+ },
306
+ restore() {
307
+ },
308
+ scale() {
309
+ },
310
+ translate() {
311
+ },
312
+ rotate() {
313
+ },
314
+ beginPath() {
315
+ },
316
+ closePath() {
317
+ },
318
+ moveTo() {
319
+ },
320
+ lineTo() {
321
+ },
322
+ arc() {
323
+ },
324
+ fill() {
325
+ },
326
+ stroke() {
327
+ },
328
+ putImageData() {
329
+ },
330
+ drawImage() {
331
+ },
332
+ setTransform() {
333
+ },
334
+ transform() {
335
+ },
336
+ rect() {
337
+ },
338
+ clip() {
339
+ }
340
+ }), s = () => {
341
+ const a = new o();
342
+ let l;
343
+ return Object.assign(a, {
344
+ width: 1,
345
+ height: 1,
346
+ style: {},
347
+ getContext: (d) => d === "2d" ? l ??= r(a) : null,
348
+ addEventListener() {
349
+ },
350
+ removeEventListener() {
351
+ },
352
+ toDataURL: () => "data:,"
353
+ });
354
+ };
355
+ e.document === void 0 && (e.document = {
356
+ createElement: (a) => a === "canvas" ? s() : { style: {}, appendChild() {
357
+ }, addEventListener() {
358
+ }, removeEventListener() {
359
+ } },
360
+ createElementNS: () => s(),
361
+ documentElement: { style: {} },
362
+ body: { appendChild() {
363
+ }, style: {} },
364
+ head: { appendChild() {
365
+ } },
366
+ addEventListener() {
367
+ },
368
+ removeEventListener() {
369
+ },
370
+ fonts: { add() {
371
+ }, load: async () => [], ready: Promise.resolve() }
372
+ }), e.OffscreenCanvas ??= class {
373
+ width;
374
+ height;
375
+ constructor(a, l) {
376
+ this.width = a ?? 1, this.height = l ?? 1;
377
+ }
378
+ getContext(a) {
379
+ return a === "2d" ? r(this) : null;
380
+ }
381
+ };
382
+ }
383
+ function te() {
384
+ const e = B.prototype;
385
+ e.loadFace = () => Promise.resolve(), e.loadGoogle = () => Promise.resolve();
386
+ }
387
+ function p(e) {
388
+ return typeof e == "number" && Number.isFinite(e);
389
+ }
390
+ const re = /createImageBitmap|source type for resource|ImageBitmap|VideoFrame|GPU|WebGL|texture|decode|fetch|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|Forbidden|\b4\d\d\b|\b5\d\d\b|network/i;
391
+ function k(e, t) {
392
+ const n = e instanceof Error ? e.message : String(e);
393
+ return e instanceof G ? !/^[./]/.test(e.specifier) ? {
394
+ severity: "error",
395
+ code: "A3",
396
+ message: `external module '${e.specifier}' is not injected — provide it via externals (the CLI ships gsap): ${n}`
397
+ } : { severity: "error", code: "A2", message: `relative import does not resolve: ${n}` } : /must export|defineComposition|builder function/i.test(n) ? { severity: "error", code: "A4", message: n } : /not implemented/i.test(n) ? {
398
+ severity: "error",
399
+ code: "B2",
400
+ message: `hit an unimplemented path (throws instead of rendering a black frame): ${n}`
401
+ } : t === "build" && re.test(n) ? {
402
+ severity: "warn",
403
+ code: "B4",
404
+ message: `builder needs a capability check runs without (media decode / network / GPU texture) — the code compiled and linked; verify the picture with \`sequio frame\`: ${n}`
405
+ } : t === "build" ? { severity: "error", code: "B1", message: `builder threw: ${n}` } : { severity: "error", code: "A1", message: n };
406
+ }
407
+ async function oe(e, t = {}) {
408
+ const n = [];
409
+ ee(), te();
410
+ const c = [], i = async (d) => (t.assetExists && !t.assetExists(d) && c.push(d), new Blob([new Uint8Array([0])])), o = new F({
411
+ files: e.files,
412
+ entry: e.entry,
413
+ externals: t.externals,
414
+ loadAsset: i
415
+ }), r = {
416
+ compositorOptions: { createRenderer: Z },
417
+ target: "server"
418
+ };
419
+ let s;
420
+ try {
421
+ s = o.link(r);
422
+ } catch (d) {
423
+ return n.push(k(d, "link")), n;
424
+ }
425
+ let a;
426
+ try {
427
+ a = await s.build(r);
428
+ } catch (d) {
429
+ return n.push(k(d, "build")), n;
430
+ }
431
+ if (!a || !(a.compositor instanceof N))
432
+ return n.push({
433
+ severity: "error",
434
+ code: "B1",
435
+ message: "builder did not return { compositor } (an initialized Compositor)"
436
+ }), n;
437
+ const l = a.compositor;
438
+ l.isInitialized || n.push({
439
+ severity: "error",
440
+ code: "B3",
441
+ message: "compositor was never initialized — add `await compositor.init()` in the builder"
442
+ });
443
+ for (const d of c)
444
+ n.push({ severity: "error", code: "C7", message: `local asset not found: ${d}` });
445
+ ne(l, a.duration, n);
446
+ try {
447
+ l.dispose();
448
+ } catch {
449
+ }
450
+ return n;
451
+ }
452
+ function ne(e, t, n) {
453
+ const c = new Set(I.families());
454
+ e.getTracks().forEach((o, r) => {
455
+ o.clips.forEach((s, a) => {
456
+ E(s, `track ${r} · clip ${a}`, t, c, n);
457
+ }), o instanceof L && o.transitions.forEach((s, a) => {
458
+ ie(s, o.clips, `track ${r} · transition ${a}`, n);
459
+ });
460
+ });
461
+ }
462
+ function E(e, t, n, c, i) {
463
+ const o = `${t} (${e.constructor.name})`;
464
+ !p(e.start) || !p(e.end) ? i.push({ severity: "error", code: "C1", message: "clip start/end is not a finite number", at: o }) : (e.start < 0 && i.push({ severity: "error", code: "C1", message: `clip start < 0 (${e.start})`, at: o }), e.end <= e.start && i.push({
465
+ severity: "error",
466
+ code: "C1",
467
+ message: `clip end ≤ start (start=${e.start}, end=${e.end})`,
468
+ at: o
469
+ }), n !== void 0 && p(n) && e.end > n && i.push({
470
+ severity: "error",
471
+ code: "C3",
472
+ message: `clip end (${e.end}) exceeds the declared duration (${n})`,
473
+ at: o
474
+ }), p(e.sourceIn) && p(e.sourceOut) && e.sourceOut > 0 && e.sourceOut < e.sourceIn && i.push({
475
+ severity: "warn",
476
+ code: "C9",
477
+ message: `sourceOut (${e.sourceOut}) is before sourceIn (${e.sourceIn})`,
478
+ at: o
479
+ })), e instanceof j && se(e, o, c, i), e instanceof O && e.children.forEach((r, s) => {
480
+ E(r, `${t} · child ${s}`, void 0, c, i);
481
+ });
482
+ }
483
+ function se(e, t, n, c) {
484
+ const i = e.start, o = e.end, r = p(i) && p(o) && o > i, s = [
485
+ { name: "position", times: e.transform.position.keyframeTimes },
486
+ { name: "scale", times: e.transform.scale.keyframeTimes },
487
+ { name: "rotation", times: e.transform.rotation.keyframeTimes },
488
+ { name: "anchor", times: e.transform.anchor.keyframeTimes },
489
+ { name: "opacity", times: e.opacity.keyframeTimes }
490
+ ];
491
+ if (e instanceof b && s.push({ name: "fontSize", times: e.fontSize.keyframeTimes }), r)
492
+ for (const { name: d, times: f } of s)
493
+ for (const u of f)
494
+ (u < i || u > o) && c.push({
495
+ severity: "error",
496
+ code: "C2",
497
+ message: `${d} keyframe at t=${u} falls outside the clip's [${i}, ${o}] — dead keyframe`,
498
+ at: t
499
+ });
500
+ const a = e.transform.anchor, l = a.keyframeTimes.length ? a.keyframeTimes : [r ? i : 0];
501
+ for (const d of l) {
502
+ const [f, u] = a.valueAt(d);
503
+ if (f < -1e-6 || f > 1 + 1e-6 || u < -1e-6 || u > 1 + 1e-6) {
504
+ c.push({
505
+ severity: "error",
506
+ code: "C8",
507
+ message: `anchor [${f}, ${u}] is outside 0..1 (anchor is normalized, not pixels)`,
508
+ at: t
509
+ });
510
+ break;
511
+ }
512
+ }
513
+ if (e instanceof b) {
514
+ const d = e.fontFamily.split(",").map((u) => u.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
515
+ !d.some((u) => Q.has(u.toLowerCase()) || n.has(u)) && d.length > 0 && c.push({
516
+ severity: "warn",
517
+ code: "C4",
518
+ message: `font-family "${e.fontFamily}" is not registered via fonts.load(...) — will fall back to a system font (preview may differ from render)`,
519
+ at: t
520
+ });
521
+ }
522
+ }
523
+ function ie(e, t, n, c) {
524
+ const { from: i, to: o } = e;
525
+ if (!i || !o) {
526
+ c.push({ severity: "error", code: "C5", message: "transition is not bound to two clips (call .between(a, b))", at: n });
527
+ return;
528
+ }
529
+ if (!t.includes(i) || !t.includes(o)) {
530
+ c.push({
531
+ severity: "error",
532
+ code: "C5",
533
+ message: "transition binds clips that are not both on this track",
534
+ at: n
535
+ });
536
+ return;
537
+ }
538
+ e.windowAt() == null && c.push({
539
+ severity: "error",
540
+ code: "C5",
541
+ message: `transition clips do not overlap — the transition window is empty (overlap [${i.start}, ${i.end}] with [${o.start}, ${o.end}])`,
542
+ at: n
543
+ });
544
+ }
545
+ function ae(e) {
546
+ return e instanceof Error ? e.message : String(e);
547
+ }
548
+ async function xe(e, t = {}) {
549
+ let n;
550
+ try {
551
+ const o = g(e), r = h(m(e));
552
+ n = await oe(o, {
553
+ externals: w(),
554
+ assetExists: (s) => $(m(r, s))
555
+ });
556
+ } catch (o) {
557
+ n = [{ severity: "error", code: "A0", message: ae(o) }];
558
+ }
559
+ const c = n.filter((o) => o.severity === "error"), i = n.filter((o) => o.severity === "warn");
560
+ if (t.json)
561
+ return console.log(JSON.stringify(n, null, 2)), c.length > 0 ? 1 : 0;
562
+ for (const o of n) {
563
+ const r = o.severity === "error" ? "✖" : "⚠", s = o.at ? ` [${o.at}]` : "";
564
+ console.error(`${r} ${o.code}${s}: ${o.message}`);
565
+ }
566
+ if (c.length === 0) {
567
+ const o = i.length ? ` (${i.length} warning${i.length === 1 ? "" : "s"})` : "";
568
+ return console.log(`✅ check passed: ${e}${o}`), 0;
569
+ }
570
+ return console.error(
571
+ `
572
+ ✖ check failed: ${c.length} error${c.length === 1 ? "" : "s"}${i.length ? `, ${i.length} warning${i.length === 1 ? "" : "s"}` : ""}.`
573
+ ), 1;
574
+ }
575
+ function y(e) {
576
+ const t = m(e);
577
+ return async (n) => {
578
+ const c = m(t, n);
579
+ if (c !== t && !c.startsWith(t + "/"))
580
+ throw new Error(`asset path escapes the project root: ${n}`);
581
+ let i;
582
+ try {
583
+ i = A(c);
584
+ } catch {
585
+ throw new Error(
586
+ `local asset not found: ${n} (looked in ${t}). Drop the file there, or reference a network URL instead.`
587
+ );
588
+ }
589
+ return new Blob([new Uint8Array(i)]);
590
+ };
591
+ }
592
+ function ce(e) {
593
+ return e.length >= 12 && Buffer.from(e.subarray(4, 8)).toString("latin1") === "ftyp" ? "mp4" : e.length >= 4 && e[0] === 26 && e[1] === 69 && e[2] === 223 && e[3] === 163 ? "webm" : null;
594
+ }
595
+ async function Ce(e, t = {}) {
596
+ const n = g(e), c = h(m(e)), { renderBundleToFile: i } = await import("@sequio/server/route-b");
597
+ try {
598
+ const o = t.out ?? "out.mp4";
599
+ console.log(`Rendering ${e} (pure Node, WebGPU)${t.scale && t.scale !== 1 ? ` @ ${t.scale}×` : ""} …`);
600
+ let r = -1;
601
+ const s = await i(n, {
602
+ out: o,
603
+ scale: t.scale,
604
+ // Make gsap (and any other CLI-provided lib) resolvable to the composition
605
+ // in the Node render, same as the browser preview does.
606
+ externals: w(),
607
+ // Resolve `loadAsset('./clip.mp4')` against the project directory on disk,
608
+ // so a local image/video renders the same as it previews (contract #3).
609
+ loadAsset: y(c),
610
+ onProgress: (a) => {
611
+ const l = Math.round(a * 100);
612
+ l !== r && l % 10 === 0 && (r = l, process.stdout.write(`\r ${l}%`));
613
+ }
614
+ });
615
+ if (process.stdout.write("\r \r"), console.log(
616
+ `✅ wrote ${s.out} (${s.frames} frames, ${s.container}/${s.videoCodec}, ${s.bytes} bytes${s.audio ? ", +audio" : ""})`
617
+ ), t.verify) {
618
+ const l = (await import("node:fs")).readFileSync(s.out), d = ce(l);
619
+ if (!d || l.length < 500)
620
+ throw new Error(`--verify failed: not a valid container (detected=${d}, size=${l.length})`);
621
+ console.log(`✅ verified: valid ${d} container.`);
622
+ }
623
+ return 0;
624
+ } catch (o) {
625
+ return console.error("✖", o instanceof Error ? o.message : String(o)), 1;
626
+ }
627
+ }
628
+ async function Ee(e, t = {}) {
629
+ const n = g(e), c = h(m(e)), { renderBundleFrameToFile: i } = await import("@sequio/server/route-b");
630
+ try {
631
+ const o = t.out ?? "frame.png", r = t.time ?? 0;
632
+ console.log(
633
+ `Rendering ${e} frame @ t=${r}s (pure Node, WebGPU)${t.scale && t.scale !== 1 ? ` @ ${t.scale}×` : ""} …`
634
+ );
635
+ const s = await i(n, {
636
+ out: o,
637
+ time: r,
638
+ scale: t.scale,
639
+ // Make gsap (and any other CLI-provided lib) resolvable to the composition
640
+ // in the Node render, same as the browser preview does.
641
+ externals: w(),
642
+ // Resolve `loadAsset('./clip.mp4')` against the project directory on disk,
643
+ // so a local image/video renders the same as it previews (contract #3).
644
+ loadAsset: y(c)
645
+ }), a = s.time !== r ? ` (clamped from ${r}s)` : "";
646
+ return console.log(
647
+ `✅ wrote ${s.out} (${s.width}×${s.height}, t=${s.time}s${a}, ${s.bytes} bytes)`
648
+ ), 0;
649
+ } catch (o) {
650
+ return console.error("✖", o instanceof Error ? o.message : String(o)), 1;
651
+ }
652
+ }
653
+ function le(e) {
654
+ const t = e?.toLowerCase().match(/\.([^./\\]+)$/), n = t?.[1] === "oga" ? "ogg" : t?.[1];
655
+ return v.includes(n) ? n : void 0;
656
+ }
657
+ async function Se(e, t = {}) {
658
+ const n = g(e), c = h(m(e)), i = t.format ?? le(t.out) ?? "mp3", { exportBundleAudioToFile: o } = await import("@sequio/server/route-b");
659
+ try {
660
+ const r = t.out ?? `out.${i}`;
661
+ console.log(`Exporting audio from ${e} (pure Node) → ${i} …`);
662
+ const s = await o(n, {
663
+ out: r,
664
+ format: i,
665
+ bitrate: t.bitrate,
666
+ // Make gsap (and any other CLI-provided lib) resolvable to the composition
667
+ // in the Node run, same as the browser preview does.
668
+ externals: w(),
669
+ // Resolve `loadAsset('./song.mp3')` against the project directory on disk,
670
+ // so a local audio source mixes the same as it previews (contract #3).
671
+ loadAsset: y(c)
672
+ });
673
+ return console.log(
674
+ `✅ wrote ${s.out} (${s.format}/${s.codec}, ${s.duration.toFixed(2)}s, ${s.bytes} bytes)`
675
+ ), 0;
676
+ } catch (r) {
677
+ return console.error("✖", r instanceof Error ? r.message : String(r)), 1;
678
+ }
679
+ }
680
+ const S = h(D(import.meta.url)), de = m(S, "../preview"), T = {};
681
+ for (const [e, t] of [
682
+ ["@sequio/engine", "../../engine/src/index.ts"],
683
+ ["@sequio/runtime", "../../runtime/src/index.ts"],
684
+ ["@sequio/cli/externals", "./externals.ts"]
685
+ ]) {
686
+ const n = m(S, t);
687
+ $(n) && (T[e] = n);
688
+ }
689
+ async function Te(e, t = {}) {
690
+ const n = m(e), c = h(n);
691
+ g(n);
692
+ const { createServer: i } = await import("vite"), o = await i({
693
+ configFile: !1,
694
+ root: de,
695
+ logLevel: "warn",
696
+ server: {
697
+ port: t.port ?? 6180,
698
+ host: t.host ? !0 : "localhost",
699
+ strictPort: !0
700
+ },
701
+ resolve: {
702
+ alias: T
703
+ },
704
+ plugins: [
705
+ {
706
+ name: "sequio-preview-bundle",
707
+ configureServer(s) {
708
+ if (s.middlewares.use("/__bundle", (a, l) => {
709
+ try {
710
+ const d = g(n);
711
+ l.setHeader("Content-Type", "application/json"), l.setHeader("Cache-Control", "no-store"), l.end(JSON.stringify(d));
712
+ } catch (d) {
713
+ l.statusCode = 500, l.setHeader("Content-Type", "application/json"), l.end(JSON.stringify({ error: d instanceof Error ? d.message : String(d) }));
714
+ }
715
+ }), s.middlewares.use("/__asset", (a, l) => {
716
+ try {
717
+ const d = (a.url ?? "").split("?")[0], f = _(decodeURIComponent(d)), u = m(c, f);
718
+ if (u !== c && !u.startsWith(c + "/"))
719
+ return l.statusCode = 403, l.end("forbidden");
720
+ if (!$(u) || !R(u).isFile())
721
+ return l.statusCode = 404, l.end(`asset not found: ${f}`);
722
+ l.setHeader("Content-Type", Y(f)), l.setHeader("Cache-Control", "no-store"), U(u).pipe(l);
723
+ } catch (d) {
724
+ l.statusCode = 400, l.end(d instanceof Error ? d.message : String(d));
725
+ }
726
+ }), t.watch) {
727
+ s.watcher.add(c);
728
+ const a = (l) => {
729
+ m(l).startsWith(c) && s.ws.send({ type: "full-reload", path: "*" });
730
+ };
731
+ s.watcher.on("change", a), s.watcher.on("add", a), s.watcher.on("unlink", a);
732
+ }
733
+ }
734
+ }
735
+ ]
736
+ });
737
+ return await o.listen(), {
738
+ url: o.resolvedUrls?.local[0] ?? `http://localhost:${t.port ?? 6180}/`,
739
+ async close() {
740
+ await o.close();
741
+ }
742
+ };
743
+ }
744
+ const ue = q(import.meta.url), fe = ue("../package.json"), Ae = fe.version;
745
+ export {
746
+ v as A,
747
+ x as D,
748
+ be as U,
749
+ Se as a,
750
+ xe as b,
751
+ oe as c,
752
+ Ee as d,
753
+ Ce as e,
754
+ Z as n,
755
+ ke as p,
756
+ g as r,
757
+ Te as s,
758
+ Ae as v
759
+ };
760
+ //# sourceMappingURL=version-C7OkFI_V.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequio/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The sequio command-line: `sequio render <file>` encodes a composition to video (pure Node, PixiJS WebGPU), `sequio frame <file>` writes a single-frame PNG, and `sequio preview <file>` serves a live in-browser preview (with --watch).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,9 +52,9 @@
52
52
  "dependencies": {
53
53
  "gsap": "^3.12.0",
54
54
  "vite": "^6.0.0",
55
- "@sequio/engine": "^0.1.1",
56
- "@sequio/server": "^0.1.0",
57
- "@sequio/runtime": "^0.1.0"
55
+ "@sequio/engine": "^0.1.2",
56
+ "@sequio/server": "^0.1.2",
57
+ "@sequio/runtime": "^0.1.2"
58
58
  },
59
59
  "scripts": {
60
60
  "dev": "tsx src/cli.ts",
@@ -1,338 +0,0 @@
1
- import { resolve as f, dirname as m, basename as S } from "node:path";
2
- import { cliExternals as h } from "./externals.js";
3
- import { readFileSync as E, existsSync as w, statSync as P, createReadStream as x } from "node:fs";
4
- import { fileURLToPath as U } from "node:url";
5
- import { resolveAssetPath as R } from "@sequio/runtime";
6
- import { NodeFileSystem as N } from "@sequio/runtime/node-fs";
7
- import { createRequire as C } from "node:module";
8
- const v = 6180, z = `sequio — programmable-timeline CLI
9
-
10
- Usage:
11
- sequio render <file> [options] Encode a composition to a video file
12
- sequio frame <file> [options] Export a single frame at a time as a PNG
13
- sequio preview <file> [options] Serve a live in-browser preview
14
-
15
- Render options (pure Node, PixiJS WebGPU — needs a GPU or Mesa lavapipe):
16
- -o, --out <path> Output path (default: out.mp4)
17
- -s, --scale <n> Render at n× the composition resolution (default: 1)
18
- --verify Assert a valid video container came out
19
-
20
- Frame options (pure Node, PixiJS WebGPU — needs a GPU or Mesa lavapipe):
21
- -t, --time <sec> Timeline time to sample, in seconds (default: 0)
22
- -o, --out <path> Output PNG path (default: frame.png)
23
- -s, --scale <n> Render at n× the composition resolution (default: 1)
24
-
25
- Preview options:
26
- -w, --watch Re-run on any project-file change (live reload)
27
- -p, --port <n> Dev-server port (default: ${v})
28
- --host Expose the server on the local network (0.0.0.0)
29
-
30
- Global:
31
- -h, --help Show this help
32
- -v, --version Print the version
33
-
34
- <file> is a TS/JS module whose default export is a
35
- defineComposition(builder) (or a bare builder). Its sibling files are
36
- bundled with it, so relative imports (./scene, ./title) resolve.`;
37
- function X(t) {
38
- if (t[0] === "--" && (t = t.slice(1)), t.length === 0) return { kind: "help" };
39
- const e = t[0];
40
- return e === "-h" || e === "--help" ? { kind: "help" } : e === "-v" || e === "--version" ? { kind: "version" } : e === "render" ? j(t.slice(1)) : e === "frame" ? A(t.slice(1)) : e === "preview" ? F(t.slice(1)) : { kind: "error", message: `Unknown command: ${e}` };
41
- }
42
- function j(t) {
43
- let e, i, l = 1, c = !1;
44
- for (let o = 0; o < t.length; o++) {
45
- const r = t[o];
46
- if (r === "-h" || r === "--help") return { kind: "help" };
47
- if (r === "-o" || r === "--out") {
48
- if (i = t[++o], i === void 0) return { kind: "error", message: `${r} needs a path` };
49
- } else if (r === "-s" || r === "--scale") {
50
- const n = t[++o], s = Number(n);
51
- if (!(s >= 1) || !Number.isFinite(s)) return { kind: "error", message: `${r} needs a number ≥ 1, got: ${n}` };
52
- l = s;
53
- } else if (r === "--verify") c = !0;
54
- else {
55
- if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
56
- if (e === void 0) e = r;
57
- else return { kind: "error", message: `Unexpected argument: ${r}` };
58
- }
59
- }
60
- return e === void 0 ? { kind: "error", message: "render needs a <file>" } : { kind: "render", file: e, out: i, scale: l, verify: c };
61
- }
62
- function A(t) {
63
- let e, i, l = 0, c = 1;
64
- for (let o = 0; o < t.length; o++) {
65
- const r = t[o];
66
- if (r === "-h" || r === "--help") return { kind: "help" };
67
- if (r === "-o" || r === "--out") {
68
- if (i = t[++o], i === void 0) return { kind: "error", message: `${r} needs a path` };
69
- } else if (r === "-t" || r === "--time") {
70
- const n = t[++o], s = Number(n);
71
- if (!Number.isFinite(s) || s < 0) return { kind: "error", message: `${r} needs a number ≥ 0, got: ${n}` };
72
- l = s;
73
- } else if (r === "-s" || r === "--scale") {
74
- const n = t[++o], s = Number(n);
75
- if (!(s >= 1) || !Number.isFinite(s)) return { kind: "error", message: `${r} needs a number ≥ 1, got: ${n}` };
76
- c = s;
77
- } else {
78
- if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
79
- if (e === void 0) e = r;
80
- else return { kind: "error", message: `Unexpected argument: ${r}` };
81
- }
82
- }
83
- return e === void 0 ? { kind: "error", message: "frame needs a <file>" } : { kind: "frame", file: e, time: l, out: i, scale: c };
84
- }
85
- function F(t) {
86
- let e, i = !1, l = !1, c = v;
87
- for (let o = 0; o < t.length; o++) {
88
- const r = t[o];
89
- if (r === "-h" || r === "--help") return { kind: "help" };
90
- if (r === "-w" || r === "--watch") i = !0;
91
- else if (r === "--host") l = !0;
92
- else if (r === "-p" || r === "--port") {
93
- const n = t[++o], s = Number(n);
94
- if (!Number.isInteger(s) || s <= 0 || s > 65535)
95
- return { kind: "error", message: `${r} needs a valid port, got: ${n}` };
96
- c = s;
97
- } else {
98
- if (r.startsWith("-")) return { kind: "error", message: `Unknown option: ${r}` };
99
- if (e === void 0) e = r;
100
- else return { kind: "error", message: `Unexpected argument: ${r}` };
101
- }
102
- }
103
- return e === void 0 ? { kind: "error", message: "preview needs a <file>" } : { kind: "preview", file: e, watch: i, port: c, host: l };
104
- }
105
- const T = /* @__PURE__ */ new Set([
106
- // images
107
- "png",
108
- "jpg",
109
- "jpeg",
110
- "gif",
111
- "webp",
112
- "avif",
113
- "bmp",
114
- "ico",
115
- // video
116
- "mp4",
117
- "webm",
118
- "mov",
119
- "mkv",
120
- "avi",
121
- "m4v",
122
- // audio
123
- "mp3",
124
- "wav",
125
- "m4a",
126
- "aac",
127
- "ogg",
128
- "oga",
129
- "flac",
130
- "opus",
131
- // fonts
132
- "ttf",
133
- "otf",
134
- "woff",
135
- "woff2"
136
- ]);
137
- function $(t) {
138
- const e = t.lastIndexOf(".");
139
- return e < 0 ? "" : t.slice(e + 1).toLowerCase();
140
- }
141
- function q(t) {
142
- return T.has($(t));
143
- }
144
- function W(t) {
145
- return {
146
- png: "image/png",
147
- jpg: "image/jpeg",
148
- jpeg: "image/jpeg",
149
- gif: "image/gif",
150
- webp: "image/webp",
151
- avif: "image/avif",
152
- bmp: "image/bmp",
153
- ico: "image/x-icon",
154
- mp4: "video/mp4",
155
- webm: "video/webm",
156
- mov: "video/quicktime",
157
- mkv: "video/x-matroska",
158
- avi: "video/x-msvideo",
159
- m4v: "video/x-m4v",
160
- mp3: "audio/mpeg",
161
- wav: "audio/wav",
162
- m4a: "audio/mp4",
163
- aac: "audio/aac",
164
- ogg: "audio/ogg",
165
- oga: "audio/ogg",
166
- flac: "audio/flac",
167
- opus: "audio/opus",
168
- ttf: "font/ttf",
169
- otf: "font/otf",
170
- woff: "font/woff",
171
- woff2: "font/woff2"
172
- }[$(t)] ?? "application/octet-stream";
173
- }
174
- function p(t) {
175
- const e = f(t), i = m(e), l = "/" + S(e), c = new N(i), o = {};
176
- for (const r of c.listFiles()) {
177
- if (q(r)) continue;
178
- const n = c.readFile(r);
179
- n !== null && (o[r] = n);
180
- }
181
- if (o[l] === void 0)
182
- throw new Error(`Entry file not found: ${t}`);
183
- return { files: o, entry: l };
184
- }
185
- function b(t) {
186
- const e = f(t);
187
- return async (i) => {
188
- const l = f(e, i);
189
- if (l !== e && !l.startsWith(e + "/"))
190
- throw new Error(`asset path escapes the project root: ${i}`);
191
- let c;
192
- try {
193
- c = E(l);
194
- } catch {
195
- throw new Error(
196
- `local asset not found: ${i} (looked in ${e}). Drop the file there, or reference a network URL instead.`
197
- );
198
- }
199
- return new Blob([new Uint8Array(c)]);
200
- };
201
- }
202
- function O(t) {
203
- return t.length >= 12 && Buffer.from(t.subarray(4, 8)).toString("latin1") === "ftyp" ? "mp4" : t.length >= 4 && t[0] === 26 && t[1] === 69 && t[2] === 223 && t[3] === 163 ? "webm" : null;
204
- }
205
- async function Y(t, e = {}) {
206
- const i = p(t), l = m(f(t)), { renderBundleToFile: c } = await import("@sequio/server/route-b");
207
- try {
208
- const o = e.out ?? "out.mp4";
209
- console.log(`Rendering ${t} (pure Node, WebGPU)${e.scale && e.scale !== 1 ? ` @ ${e.scale}×` : ""} …`);
210
- let r = -1;
211
- const n = await c(i, {
212
- out: o,
213
- scale: e.scale,
214
- // Make gsap (and any other CLI-provided lib) resolvable to the composition
215
- // in the Node render, same as the browser preview does.
216
- externals: h(),
217
- // Resolve `loadAsset('./clip.mp4')` against the project directory on disk,
218
- // so a local image/video renders the same as it previews (contract #3).
219
- loadAsset: b(l),
220
- onProgress: (s) => {
221
- const a = Math.round(s * 100);
222
- a !== r && a % 10 === 0 && (r = a, process.stdout.write(`\r ${a}%`));
223
- }
224
- });
225
- if (process.stdout.write("\r \r"), console.log(
226
- `✅ wrote ${n.out} (${n.frames} frames, ${n.container}/${n.videoCodec}, ${n.bytes} bytes${n.audio ? ", +audio" : ""})`
227
- ), e.verify) {
228
- const a = (await import("node:fs")).readFileSync(n.out), d = O(a);
229
- if (!d || a.length < 500)
230
- throw new Error(`--verify failed: not a valid container (detected=${d}, size=${a.length})`);
231
- console.log(`✅ verified: valid ${d} container.`);
232
- }
233
- return 0;
234
- } catch (o) {
235
- return console.error("✖", o instanceof Error ? o.message : String(o)), 1;
236
- }
237
- }
238
- async function K(t, e = {}) {
239
- const i = p(t), l = m(f(t)), { renderBundleFrameToFile: c } = await import("@sequio/server/route-b");
240
- try {
241
- const o = e.out ?? "frame.png", r = e.time ?? 0;
242
- console.log(
243
- `Rendering ${t} frame @ t=${r}s (pure Node, WebGPU)${e.scale && e.scale !== 1 ? ` @ ${e.scale}×` : ""} …`
244
- );
245
- const n = await c(i, {
246
- out: o,
247
- time: r,
248
- scale: e.scale,
249
- // Make gsap (and any other CLI-provided lib) resolvable to the composition
250
- // in the Node render, same as the browser preview does.
251
- externals: h(),
252
- // Resolve `loadAsset('./clip.mp4')` against the project directory on disk,
253
- // so a local image/video renders the same as it previews (contract #3).
254
- loadAsset: b(l)
255
- }), s = n.time !== r ? ` (clamped from ${r}s)` : "";
256
- return console.log(
257
- `✅ wrote ${n.out} (${n.width}×${n.height}, t=${n.time}s${s}, ${n.bytes} bytes)`
258
- ), 0;
259
- } catch (o) {
260
- return console.error("✖", o instanceof Error ? o.message : String(o)), 1;
261
- }
262
- }
263
- const k = m(U(import.meta.url)), _ = f(k, "../preview"), y = {};
264
- for (const [t, e] of [
265
- ["@sequio/engine", "../../engine/src/index.ts"],
266
- ["@sequio/runtime", "../../runtime/src/index.ts"],
267
- ["@sequio/cli/externals", "./externals.ts"]
268
- ]) {
269
- const i = f(k, e);
270
- w(i) && (y[t] = i);
271
- }
272
- async function Q(t, e = {}) {
273
- const i = f(t), l = m(i);
274
- p(i);
275
- const { createServer: c } = await import("vite"), o = await c({
276
- configFile: !1,
277
- root: _,
278
- logLevel: "warn",
279
- server: {
280
- port: e.port ?? 6180,
281
- host: e.host ? !0 : "localhost",
282
- strictPort: !0
283
- },
284
- resolve: {
285
- alias: y
286
- },
287
- plugins: [
288
- {
289
- name: "sequio-preview-bundle",
290
- configureServer(n) {
291
- if (n.middlewares.use("/__bundle", (s, a) => {
292
- try {
293
- const d = p(i);
294
- a.setHeader("Content-Type", "application/json"), a.setHeader("Cache-Control", "no-store"), a.end(JSON.stringify(d));
295
- } catch (d) {
296
- a.statusCode = 500, a.setHeader("Content-Type", "application/json"), a.end(JSON.stringify({ error: d instanceof Error ? d.message : String(d) }));
297
- }
298
- }), n.middlewares.use("/__asset", (s, a) => {
299
- try {
300
- const d = (s.url ?? "").split("?")[0], g = R(decodeURIComponent(d)), u = f(l, g);
301
- if (u !== l && !u.startsWith(l + "/"))
302
- return a.statusCode = 403, a.end("forbidden");
303
- if (!w(u) || !P(u).isFile())
304
- return a.statusCode = 404, a.end(`asset not found: ${g}`);
305
- a.setHeader("Content-Type", W(g)), a.setHeader("Cache-Control", "no-store"), x(u).pipe(a);
306
- } catch (d) {
307
- a.statusCode = 400, a.end(d instanceof Error ? d.message : String(d));
308
- }
309
- }), e.watch) {
310
- n.watcher.add(l);
311
- const s = (a) => {
312
- f(a).startsWith(l) && n.ws.send({ type: "full-reload", path: "*" });
313
- };
314
- n.watcher.on("change", s), n.watcher.on("add", s), n.watcher.on("unlink", s);
315
- }
316
- }
317
- }
318
- ]
319
- });
320
- return await o.listen(), {
321
- url: o.resolvedUrls?.local[0] ?? `http://localhost:${e.port ?? 6180}/`,
322
- async close() {
323
- await o.close();
324
- }
325
- };
326
- }
327
- const G = C(import.meta.url), I = G("../package.json"), Z = I.version;
328
- export {
329
- v as D,
330
- z as U,
331
- K as a,
332
- Y as b,
333
- X as p,
334
- p as r,
335
- Q as s,
336
- Z as v
337
- };
338
- //# sourceMappingURL=version-B2TKvZen.js.map