@glissade/cli 0.14.0-pre.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4 -0
- package/dist/index.d.ts +19 -0
- package/dist/locale.js +38 -1
- package/dist/render.js +37 -8
- package/dist/shards.js +2 -1
- package/package.json +10 -10
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ const KNOWN_BOOLEAN_FLAGS = new Set([
|
|
|
17
17
|
"no-warnings",
|
|
18
18
|
"lossless-intermediate",
|
|
19
19
|
"allow-gpu-shards",
|
|
20
|
+
"allow-system-fonts",
|
|
20
21
|
"verbose",
|
|
21
22
|
"allow-degraded",
|
|
22
23
|
"bisect",
|
|
@@ -110,6 +111,8 @@ render options:
|
|
|
110
111
|
(YouTube needs the 1st chapter at 0:00 — auto-anchored — and each chapter >= 10s; author cue ts accordingly)
|
|
111
112
|
--chapters-kind <k[,k]> cue kinds that become VTT chapters (default: chapter); cues.json keeps all kinds
|
|
112
113
|
--strict fail on an unregistered font family or an uncovered glyph (§3.6; default: warn)
|
|
114
|
+
--allow-system-fonts exempt true-OS-installed families from the unregistered-family check (host-dependent;
|
|
115
|
+
off by default, IGNORED under --strict so the strict verdict stays host-independent; §3.6)
|
|
113
116
|
--locale <code> resolve the scene against messages.<code>.json (node-id text + free-standing t() keys) and prefer
|
|
114
117
|
the <base>.<code>.narration.timing.json sibling (0.14). No --locale resolves the BASE files
|
|
115
118
|
|
|
@@ -456,6 +459,7 @@ async function main() {
|
|
|
456
459
|
...flags.has("state") ? { state: flags.get("state") } : {},
|
|
457
460
|
...flags.has("force") ? { force: true } : {},
|
|
458
461
|
...flags.has("strict") ? { strictFonts: true } : {},
|
|
462
|
+
...flags.has("allow-system-fonts") ? { allowSystemFonts: true } : {},
|
|
459
463
|
...workers !== void 0 ? { workers } : {},
|
|
460
464
|
...flags.has("lossless-intermediate") ? { losslessIntermediate: true } : {},
|
|
461
465
|
...flags.has("allow-gpu-shards") ? { allowGpuShards: true } : {},
|
package/dist/index.d.ts
CHANGED
|
@@ -168,6 +168,14 @@ interface RenderOptions {
|
|
|
168
168
|
chapterKinds?: ReadonlySet<string>;
|
|
169
169
|
/** --strict: font validation throws on an unregistered family / missing glyph (§3.6). Default dev-warn. */
|
|
170
170
|
strictFonts?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* --allow-system-fonts (§3.6, 0.14 FIX 6): exempt true-OS-installed families
|
|
173
|
+
* (the host `GlobalFonts.families` catalog) from the unregistered-family check.
|
|
174
|
+
* OFF by default — the exempt set is otherwise just the families glissade
|
|
175
|
+
* registered from `doc.assets`, so the verdict is host-independent. IGNORED
|
|
176
|
+
* under --strict (strict stays host-independent regardless of this flag).
|
|
177
|
+
*/
|
|
178
|
+
allowSystemFonts?: boolean;
|
|
171
179
|
/**
|
|
172
180
|
* --workers N (§5.6): split the frame range into N contiguous sub-ranges, render
|
|
173
181
|
* each in a separate `gs` child process, and join the shard videos. Ignored for
|
|
@@ -209,6 +217,17 @@ interface RenderOptions {
|
|
|
209
217
|
locale?: string;
|
|
210
218
|
onProgress?: (frame: number, total: number) => void;
|
|
211
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Build the case-folded set of families EXEMPT from the §3.6 unregistered-family
|
|
222
|
+
* check (FIX 6, 0.14). Seeded from the families glissade actually registered out
|
|
223
|
+
* of `doc.assets` (`registered`, already lower-cased) — NEVER the true-OS
|
|
224
|
+
* `GlobalFonts.families` catalog, which is host-dependent (3 families on a clean
|
|
225
|
+
* Linux CI box, hundreds on a dev macOS) and would make the --strict PASS/FAIL
|
|
226
|
+
* verdict depend on the host. The true-OS `osCatalog` is folded in ONLY when
|
|
227
|
+
* `allowSystemFonts` is set AND `strict` is false — so --strict stays host-
|
|
228
|
+
* independent regardless of the flag. Pure: no I/O, no host queries.
|
|
229
|
+
*/
|
|
230
|
+
|
|
212
231
|
declare class SceneModuleError extends Error {
|
|
213
232
|
constructor(modulePath: string, detail: string);
|
|
214
233
|
}
|
package/dist/locale.js
CHANGED
|
@@ -9,6 +9,18 @@ import { dirname, join } from "node:path";
|
|
|
9
9
|
* resolves the BASE files (`messages.json` is NOT consulted, the base narration
|
|
10
10
|
* sibling is used) → byte-identical to today. Every locale is opt-in.
|
|
11
11
|
*/
|
|
12
|
+
/** Strip a scene module's `.ts`/`.tsx`/`.js`/`.jsx` extension → the file stem. */
|
|
13
|
+
function moduleStem(modulePath) {
|
|
14
|
+
return modulePath.replace(/\.[jt]sx?$/, "");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The locale-tagged NARRATION timing sibling suffix. A `--locale xx` render
|
|
18
|
+
* prefers `<base>.xx.narration.timing.json` over the base
|
|
19
|
+
* `<base>.narration.timing.json`. DEFAULT convention — the maintainer is
|
|
20
|
+
* confirming the exact suffix with the ai-training consumer; changing it is a
|
|
21
|
+
* one-line edit HERE. `%s` is the locale code.
|
|
22
|
+
*/
|
|
23
|
+
const LOCALE_NARRATION_SUFFIX = ".%s.narration.timing.json";
|
|
12
24
|
/**
|
|
13
25
|
* The per-locale message table filename, resolved RELATIVE to the scene module
|
|
14
26
|
* directory: `messages.<locale>.json`. DEFAULT convention; one-line change.
|
|
@@ -26,5 +38,30 @@ function loadMessageTable(modulePath, locale) {
|
|
|
26
38
|
if (!existsSync(file)) return void 0;
|
|
27
39
|
return JSON.parse(readFileSync(file, "utf8"));
|
|
28
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The locale-tagged narration timing sibling path for a module, or `undefined`
|
|
43
|
+
* when no `locale` is requested. The render path prefers this sibling when it
|
|
44
|
+
* exists, falling back to the base sibling otherwise (so a locale that reuses
|
|
45
|
+
* the base narration still renders).
|
|
46
|
+
*/
|
|
47
|
+
function localeNarrationPathFor(modulePath, locale) {
|
|
48
|
+
return moduleStem(modulePath) + LOCALE_NARRATION_SUFFIX.replace("%s", locale);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A declared `--locale <code>` resolved to NEITHER a `messages.<code>.json` nor a
|
|
52
|
+
* `<base>.<code>.narration.timing.json` sibling — the locale has no assets at
|
|
53
|
+
* all, so a render would silently fall back to the BASE artifact (wrong-language
|
|
54
|
+
* output, exit 0, no warning). Render hard-throws this instead.
|
|
55
|
+
*
|
|
56
|
+
* (A narration-only locale legitimately has no messages file, and a
|
|
57
|
+
* messages-only locale legitimately reuses the base narration — so this fires
|
|
58
|
+
* only when BOTH are absent.)
|
|
59
|
+
*/
|
|
60
|
+
var UnknownLocaleError = class extends Error {
|
|
61
|
+
constructor(locale, messagesPath, narrationPath) {
|
|
62
|
+
super(`--locale '${locale}': no locale assets found — neither a message table nor a narration sibling resolves. Looked for '${messagesPath}' and '${narrationPath}'. Add one of those files, or drop --locale to render the base language.`);
|
|
63
|
+
this.name = "UnknownLocaleError";
|
|
64
|
+
}
|
|
65
|
+
};
|
|
29
66
|
//#endregion
|
|
30
|
-
export { loadMessageTable };
|
|
67
|
+
export { UnknownLocaleError, loadMessageTable, localeNarrationPathFor, messagesFileFor };
|
package/dist/render.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime.js";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import { createJiti } from "jiti";
|
|
9
9
|
import { buildFontRegistry } from "@glissade/core";
|
|
10
|
-
import { evaluate, validateSceneFonts, withDeterminismGuards } from "@glissade/scene";
|
|
10
|
+
import { collectLocalizedTextUsages, evaluate, validateSceneFonts, withDeterminismGuards } from "@glissade/scene";
|
|
11
11
|
import { SkiaBackend } from "@glissade/backend-skia";
|
|
12
12
|
//#region src/assetValidation.ts
|
|
13
13
|
/**
|
|
@@ -70,6 +70,7 @@ function validateAssetReferences(refs, declaredIds) {
|
|
|
70
70
|
*/
|
|
71
71
|
var render_exports = /* @__PURE__ */ __exportAll({
|
|
72
72
|
SceneModuleError: () => SceneModuleError,
|
|
73
|
+
buildFontExemptSet: () => buildFontExemptSet,
|
|
73
74
|
buildMixWav: () => buildMixWav,
|
|
74
75
|
collectAudioClips: () => collectAudioClips,
|
|
75
76
|
collectMixAudioInputs: () => collectMixAudioInputs,
|
|
@@ -80,6 +81,21 @@ var render_exports = /* @__PURE__ */ __exportAll({
|
|
|
80
81
|
render: () => render,
|
|
81
82
|
resolveLoudnessGainDb: () => resolveLoudnessGainDb
|
|
82
83
|
});
|
|
84
|
+
/**
|
|
85
|
+
* Build the case-folded set of families EXEMPT from the §3.6 unregistered-family
|
|
86
|
+
* check (FIX 6, 0.14). Seeded from the families glissade actually registered out
|
|
87
|
+
* of `doc.assets` (`registered`, already lower-cased) — NEVER the true-OS
|
|
88
|
+
* `GlobalFonts.families` catalog, which is host-dependent (3 families on a clean
|
|
89
|
+
* Linux CI box, hundreds on a dev macOS) and would make the --strict PASS/FAIL
|
|
90
|
+
* verdict depend on the host. The true-OS `osCatalog` is folded in ONLY when
|
|
91
|
+
* `allowSystemFonts` is set AND `strict` is false — so --strict stays host-
|
|
92
|
+
* independent regardless of the flag. Pure: no I/O, no host queries.
|
|
93
|
+
*/
|
|
94
|
+
function buildFontExemptSet(registered, opts) {
|
|
95
|
+
const exempt = new Set(registered);
|
|
96
|
+
if (opts.allowSystemFonts && !opts.strict) for (const f of opts.osCatalog) exempt.add(f);
|
|
97
|
+
return exempt;
|
|
98
|
+
}
|
|
83
99
|
var SceneModuleError = class extends Error {
|
|
84
100
|
constructor(modulePath, detail) {
|
|
85
101
|
super(`${modulePath}: ${detail}\nA scene module default-exports { createScene(): Scene, timeline: Timeline } (SceneModule).`);
|
|
@@ -123,11 +139,16 @@ async function render(opts) {
|
|
|
123
139
|
...opts.force !== void 0 ? { force: opts.force } : {}
|
|
124
140
|
});
|
|
125
141
|
if (opts.locale !== void 0 && opts.locale !== "") {
|
|
126
|
-
const { loadMessageTable } = await import("./locale.js");
|
|
142
|
+
const { loadMessageTable, messagesFileFor, localeNarrationPathFor, UnknownLocaleError } = await import("./locale.js");
|
|
127
143
|
const table = loadMessageTable(opts.modulePath, opts.locale);
|
|
144
|
+
const narrationPath = localeNarrationPathFor(opts.modulePath, opts.locale);
|
|
145
|
+
if (!table && !existsSync(narrationPath)) throw new UnknownLocaleError(opts.locale, messagesFileFor(opts.modulePath, opts.locale), narrationPath);
|
|
128
146
|
if (table) {
|
|
129
|
-
const { localize } = await import("@glissade/core/i18n");
|
|
130
|
-
doc = localize(doc, table, {
|
|
147
|
+
const { localize, getConsumedMessageIds } = await import("@glissade/core/i18n");
|
|
148
|
+
doc = localize(doc, table, {
|
|
149
|
+
locale: opts.locale,
|
|
150
|
+
consumedIds: getConsumedMessageIds()
|
|
151
|
+
});
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
const fps = opts.fps ?? doc.fps ?? 60;
|
|
@@ -185,11 +206,13 @@ async function render(opts) {
|
|
|
185
206
|
const { createHash } = await import("node:crypto");
|
|
186
207
|
const assetDigests = /* @__PURE__ */ new Map();
|
|
187
208
|
const digestBytes = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
209
|
+
const registeredFamilies = /* @__PURE__ */ new Set();
|
|
188
210
|
const fontRegistry = buildFontRegistry(doc.assets);
|
|
189
211
|
if (fontRegistry.faces().length > 0) {
|
|
190
212
|
const { GlobalFonts } = await import("@napi-rs/canvas");
|
|
191
213
|
let ingest;
|
|
192
214
|
for (const face of fontRegistry.faces()) {
|
|
215
|
+
registeredFamilies.add(face.family.toLowerCase());
|
|
193
216
|
const path = resolveAsset(face.url, opts.modulePath);
|
|
194
217
|
if (/\.woff2?$/i.test(face.url)) {
|
|
195
218
|
ingest ??= await import("@glissade/core/font-ingest");
|
|
@@ -206,8 +229,13 @@ async function render(opts) {
|
|
|
206
229
|
}
|
|
207
230
|
}
|
|
208
231
|
}
|
|
209
|
-
const
|
|
210
|
-
const osFamilies =
|
|
232
|
+
const osCatalog = !!opts.allowSystemFonts && !opts.strictFonts ? new Set((await import("@napi-rs/canvas")).GlobalFonts.families.map((f) => f.family.toLowerCase())) : /* @__PURE__ */ new Set();
|
|
233
|
+
const osFamilies = buildFontExemptSet(registeredFamilies, {
|
|
234
|
+
allowSystemFonts: !!opts.allowSystemFonts,
|
|
235
|
+
strict: !!opts.strictFonts,
|
|
236
|
+
osCatalog
|
|
237
|
+
});
|
|
238
|
+
const localizedUsages = opts.locale !== void 0 && opts.locale !== "" ? collectLocalizedTextUsages(scene, doc) : [];
|
|
211
239
|
await validateSceneFonts(scene, doc, async (url) => {
|
|
212
240
|
try {
|
|
213
241
|
const buf = await readFile(resolveAsset(url, opts.modulePath));
|
|
@@ -217,7 +245,8 @@ async function render(opts) {
|
|
|
217
245
|
}
|
|
218
246
|
}, {
|
|
219
247
|
mode: opts.strictFonts ? "strict" : "dev",
|
|
220
|
-
osFamilies
|
|
248
|
+
osFamilies,
|
|
249
|
+
extraUsages: localizedUsages
|
|
221
250
|
});
|
|
222
251
|
for (const [assetId, ref] of Object.entries(doc.assets ?? {})) if (ref.kind === "font") {} else if (ref.kind === "image") {
|
|
223
252
|
const { loadImage } = await import("@napi-rs/canvas");
|
package/dist/shards.js
CHANGED
|
@@ -149,7 +149,8 @@ async function renderSharded(a) {
|
|
|
149
149
|
...opts.trace !== void 0 ? ["--trace", opts.trace] : [],
|
|
150
150
|
...opts.state !== void 0 ? ["--state", opts.state] : [],
|
|
151
151
|
...opts.force ? ["--force"] : [],
|
|
152
|
-
...opts.strictFonts ? ["--strict"] : []
|
|
152
|
+
...opts.strictFonts ? ["--strict"] : [],
|
|
153
|
+
...opts.allowSystemFonts ? ["--allow-system-fonts"] : []
|
|
153
154
|
];
|
|
154
155
|
const child = spawnSync(process.execPath, childArgs, { stdio: [
|
|
155
156
|
"ignore",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/cli",
|
|
3
|
-
"version": "0.14.0
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "glissade CLI: headless rendering via backend-skia (+ FFmpeg mux).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -23,15 +23,15 @@
|
|
|
23
23
|
"@napi-rs/canvas": "^0.1.65",
|
|
24
24
|
"esbuild": "^0.28.0",
|
|
25
25
|
"jiti": "^2.4.2",
|
|
26
|
-
"@glissade/backend-skia": "0.14.0
|
|
27
|
-
"@glissade/core": "0.14.0
|
|
28
|
-
"@glissade/interact": "0.14.0
|
|
29
|
-
"@glissade/lottie": "0.14.0
|
|
30
|
-
"@glissade/svg": "0.14.0
|
|
31
|
-
"@glissade/narrate": "0.14.0
|
|
32
|
-
"@glissade/player": "0.14.0
|
|
33
|
-
"@glissade/scene": "0.14.0
|
|
34
|
-
"@glissade/sfx": "0.14.0
|
|
26
|
+
"@glissade/backend-skia": "0.14.0",
|
|
27
|
+
"@glissade/core": "0.14.0",
|
|
28
|
+
"@glissade/interact": "0.14.0",
|
|
29
|
+
"@glissade/lottie": "0.14.0",
|
|
30
|
+
"@glissade/svg": "0.14.0",
|
|
31
|
+
"@glissade/narrate": "0.14.0",
|
|
32
|
+
"@glissade/player": "0.14.0",
|
|
33
|
+
"@glissade/scene": "0.14.0",
|
|
34
|
+
"@glissade/sfx": "0.14.0"
|
|
35
35
|
},
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|