@glissade/cli 0.42.0-pre.0 → 0.42.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 -3
- package/dist/localize.js +35 -14
- package/package.json +10 -10
package/dist/cli.js
CHANGED
|
@@ -92,7 +92,7 @@ const USAGE = `usage:
|
|
|
92
92
|
gs describe [--out <api.json>] [--examples] snapshot THIS engine's describe() API manifest (stdout, or --out to a file) — the input to gs migrate
|
|
93
93
|
gs migrate <baseline-api.json> [--json] [--check] diff a saved API manifest against the current engine: moved imports / removed / added / changed, with a suggested fix per breaking item (advisory; --check exits non-zero on any breaking change for CI gating)
|
|
94
94
|
gs repin <scene-module> --golden <dir> [--name <p>] [--frames a,b,..] [--fps <n>] [--since <ref>] [--write] [--only a,b] [--heatmap <dir>] [--floor <ssim>] [--force] narration-aware golden reviewer: render current vs committed goldens, report perceptual delta + the re-narration cause, re-pin only frames you allow (default dry-run; --floor refuses a bigger-than-expected drop)
|
|
95
|
-
gs localize <scene-module> --to <locale> [--from <locale>] [--write] [--keep-voice] [--json] fork a narration into a new locale (clone segment/pause structure, PRESERVING beat ids so .start() anchors survive) + stub messages.<locale>.json from the scene's t() ids, running the render path's parity + localize checks BEFORE any TTS. Default dry-run (exits non-zero on drift); --write emits <base>.<locale>.narration.json + messages.<locale>.json
|
|
95
|
+
gs localize <scene-module> --to <locale> [--from <locale>] [--write] [--strict] [--keep-voice] [--json] fork a narration into a new locale (clone segment/pause structure, PRESERVING beat ids so .start() anchors survive) + stub messages.<locale>.json from the scene's t() ids, running the render path's parity + localize checks BEFORE any TTS. Default dry-run (exits non-zero on drift); --write emits <base>.<locale>.narration.json + messages.<locale>.json (re-localize CARRIES existing translations over — never clobbers); --strict refuses to write on a preflight failure
|
|
96
96
|
gs --version print the engine version
|
|
97
97
|
|
|
98
98
|
render options:
|
|
@@ -367,10 +367,11 @@ async function main() {
|
|
|
367
367
|
to,
|
|
368
368
|
...lf.get("from") ? { from: lf.get("from") } : {},
|
|
369
369
|
...lf.has("write") ? { write: true } : {},
|
|
370
|
-
...lf.has("keep-voice") ? { keepVoice: true } : {}
|
|
370
|
+
...lf.has("keep-voice") ? { keepVoice: true } : {},
|
|
371
|
+
...lf.has("strict") ? { strict: true } : {}
|
|
371
372
|
});
|
|
372
373
|
process.stdout.write(lf.has("json") ? `${JSON.stringify(report, null, 2)}\n` : `${formatLocalizeReport(report)}\n`);
|
|
373
|
-
if (!lf.has("write")
|
|
374
|
+
if (report.preflight.issues.length > 0 && (!lf.has("write") || lf.has("strict"))) process.exit(1);
|
|
374
375
|
} catch (err) {
|
|
375
376
|
fail(err instanceof Error ? err.message : String(err));
|
|
376
377
|
}
|
package/dist/localize.js
CHANGED
|
@@ -32,10 +32,17 @@ var LocalizeError = class extends Error {
|
|
|
32
32
|
* `keepVoice` is set.
|
|
33
33
|
*/
|
|
34
34
|
function forkNarrationScript(base, opts = {}) {
|
|
35
|
+
const existingText = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const el of opts.existing?.segments ?? []) if (!isPause(el) && el.text !== "") existingText.set(el.id, el.text);
|
|
35
37
|
const segments = base.segments.map((el) => {
|
|
36
38
|
if (isPause(el)) return { ...el };
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
+
const carried = existingText.get(el.id);
|
|
40
|
+
const withText = carried !== void 0 && carried !== el.text ? {
|
|
41
|
+
...el,
|
|
42
|
+
text: carried
|
|
43
|
+
} : { ...el };
|
|
44
|
+
if (opts.keepVoice) return withText;
|
|
45
|
+
const { voice: _voice, ...rest } = withText;
|
|
39
46
|
return rest;
|
|
40
47
|
});
|
|
41
48
|
const forked = {
|
|
@@ -155,7 +162,11 @@ async function harvestMessageIds(modulePath) {
|
|
|
155
162
|
const tIds = new Set(getConsumedMessageIds());
|
|
156
163
|
const ids = new Set(tIds);
|
|
157
164
|
const doc = mod.timeline;
|
|
158
|
-
for (const tr of doc.tracks ?? [])
|
|
165
|
+
for (const tr of doc.tracks ?? []) {
|
|
166
|
+
if (tr.type !== "string") continue;
|
|
167
|
+
if (new Set((tr.keys ?? []).map((k) => k.value)).size > 1) continue;
|
|
168
|
+
ids.add(nodeIdOf(tr.target));
|
|
169
|
+
}
|
|
159
170
|
return {
|
|
160
171
|
ids: [...ids],
|
|
161
172
|
tIds,
|
|
@@ -187,11 +198,15 @@ async function localizeCommand(modulePath, opts) {
|
|
|
187
198
|
baseScript = scriptFromTiming(JSON.parse(readFileSync(baseTimingPath, "utf8")));
|
|
188
199
|
narrationSource = "timing";
|
|
189
200
|
}
|
|
190
|
-
const forked = baseScript ? forkNarrationScript(baseScript, { keepVoice: opts.keepVoice ?? false }) : void 0;
|
|
191
|
-
const beatIds = baseScript ? scriptBeatIds(baseScript) : [];
|
|
192
201
|
const narrationPath = moduleStemOf(modulePath) + `.${to}.narration.json`;
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
const existingLocale = baseScript && existsSync(narrationPath) ? JSON.parse(readFileSync(narrationPath, "utf8")) : void 0;
|
|
203
|
+
const forked = baseScript ? forkNarrationScript(baseScript, {
|
|
204
|
+
keepVoice: opts.keepVoice ?? false,
|
|
205
|
+
...existingLocale ? { existing: existingLocale } : {}
|
|
206
|
+
}) : void 0;
|
|
207
|
+
const beatIds = baseScript ? scriptBeatIds(baseScript) : [];
|
|
208
|
+
const localeBeatIds = existingLocale ? scriptBeatIds(existingLocale) : beatIds;
|
|
209
|
+
const carriedSegments = baseScript && forked ? forked.segments.filter((el, i) => !isPause(el) && el.text !== baseScript.segments[i].text).length : 0;
|
|
195
210
|
const base = opts.from ? loadMessageTable(modulePath, opts.from) : void 0;
|
|
196
211
|
const messagesPath = messagesFileFor(modulePath, to);
|
|
197
212
|
const existingTable = existsSync(messagesPath) ? JSON.parse(readFileSync(messagesPath, "utf8")) : loadMessageTable(modulePath, to);
|
|
@@ -214,20 +229,25 @@ async function localizeCommand(modulePath, opts) {
|
|
|
214
229
|
consumedIds: tIds
|
|
215
230
|
});
|
|
216
231
|
const wrote = [];
|
|
217
|
-
|
|
232
|
+
const refusedWrite = !!(opts.write && opts.strict && !preflight.ok);
|
|
233
|
+
if (opts.write && !refusedWrite) {
|
|
218
234
|
if (forked) {
|
|
219
235
|
writeFileSync(narrationPath, JSON.stringify(forked, null, 2) + "\n");
|
|
220
236
|
wrote.push(narrationPath);
|
|
221
237
|
}
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
if (messageIds.length > 0) {
|
|
239
|
+
writeFileSync(messagesPath, JSON.stringify(stubTable, null, 2) + "\n");
|
|
240
|
+
wrote.push(messagesPath);
|
|
241
|
+
}
|
|
224
242
|
}
|
|
225
243
|
return {
|
|
226
244
|
locale: to,
|
|
227
245
|
messageIds: messageIds.sort(),
|
|
228
246
|
beatIds,
|
|
247
|
+
carriedSegments,
|
|
229
248
|
preflight,
|
|
230
249
|
wrote,
|
|
250
|
+
refusedWrite,
|
|
231
251
|
narrationPath,
|
|
232
252
|
messagesPath,
|
|
233
253
|
narrationSource
|
|
@@ -237,15 +257,16 @@ async function localizeCommand(modulePath, opts) {
|
|
|
237
257
|
function formatLocalizeReport(r) {
|
|
238
258
|
const lines = [];
|
|
239
259
|
lines.push(`gs localize → ${r.locale}`);
|
|
240
|
-
lines.push(` narration: ${r.narrationSource === "none" ? "none found" : `${r.beatIds.length} beat(s) from the ${r.narrationSource} (ids preserved)`}`);
|
|
241
|
-
lines.push(` messages: ${r.messageIds.length} id(s) harvested, ${r.preflight.untranslated} still untranslated`);
|
|
260
|
+
lines.push(` narration: ${r.narrationSource === "none" ? "none found" : `${r.beatIds.length} beat(s) from the ${r.narrationSource} (ids preserved)`}` + (r.carriedSegments > 0 ? `; ${r.carriedSegments} translation(s) carried over` : ""));
|
|
261
|
+
lines.push(r.messageIds.length === 0 ? " messages: none localizable (no t() ids / no single-cue string tracks) — narration only" : ` messages: ${r.messageIds.length} id(s) harvested, ${r.preflight.untranslated} still untranslated`);
|
|
242
262
|
if (r.preflight.issues.length === 0) lines.push(" preflight: ✓ parity + localize clean (safe to translate + narrate)");
|
|
243
263
|
else {
|
|
244
264
|
lines.push(` preflight: ✗ ${r.preflight.issues.length} issue(s) — fix before narrating:`);
|
|
245
265
|
for (const i of r.preflight.issues) lines.push(` [${i.kind}] ${i.message.replace(/\n/g, "\n ")}`);
|
|
246
266
|
}
|
|
247
|
-
if (r.
|
|
248
|
-
else lines.push(`
|
|
267
|
+
if (r.refusedWrite) lines.push(" --strict: refused to write (preflight failed); fix the drift above, then re-run");
|
|
268
|
+
else if (r.wrote.length) lines.push(` wrote: ${r.wrote.join(", ")}`);
|
|
269
|
+
else lines.push(` (dry run — re-run with --write to emit ${r.narrationPath}${r.messageIds.length ? ` + ${r.messagesPath}` : ""})`);
|
|
249
270
|
return lines.join("\n");
|
|
250
271
|
}
|
|
251
272
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/cli",
|
|
3
|
-
"version": "0.42.0
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "glissade CLI: headless rendering via backend-skia (+ FFmpeg mux).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"@napi-rs/canvas": "^0.1.65",
|
|
29
29
|
"esbuild": "0.28.0",
|
|
30
30
|
"jiti": "^2.4.2",
|
|
31
|
-
"@glissade/backend-skia": "0.42.0
|
|
32
|
-
"@glissade/core": "0.42.0
|
|
33
|
-
"@glissade/interact": "0.42.0
|
|
34
|
-
"@glissade/lottie": "0.42.0
|
|
35
|
-
"@glissade/narrate": "0.42.0
|
|
36
|
-
"@glissade/player": "0.42.0
|
|
37
|
-
"@glissade/scene": "0.42.0
|
|
38
|
-
"@glissade/sfx": "0.42.0
|
|
39
|
-
"@glissade/svg": "0.42.0
|
|
31
|
+
"@glissade/backend-skia": "0.42.0",
|
|
32
|
+
"@glissade/core": "0.42.0",
|
|
33
|
+
"@glissade/interact": "0.42.0",
|
|
34
|
+
"@glissade/lottie": "0.42.0",
|
|
35
|
+
"@glissade/narrate": "0.42.0",
|
|
36
|
+
"@glissade/player": "0.42.0",
|
|
37
|
+
"@glissade/scene": "0.42.0",
|
|
38
|
+
"@glissade/sfx": "0.42.0",
|
|
39
|
+
"@glissade/svg": "0.42.0"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|