@patterkit/ops 1.0.1 → 1.1.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/index.d.ts +189 -9
- package/dist/index.js +567 -152
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -623,7 +623,7 @@ function merge3(base, ours, theirs, id, path, conflicts) {
|
|
|
623
623
|
conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
|
|
624
624
|
return ours;
|
|
625
625
|
}
|
|
626
|
-
function mergeMap(base, ours, theirs, prefix, conflicts,
|
|
626
|
+
function mergeMap(base, ours, theirs, prefix, conflicts, resolve9) {
|
|
627
627
|
const out = {};
|
|
628
628
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
629
629
|
const b = base[k], o = ours[k], t = theirs[k];
|
|
@@ -632,7 +632,7 @@ function mergeMap(base, ours, theirs, prefix, conflicts, resolve8) {
|
|
|
632
632
|
else if (eq(b, o)) v = t;
|
|
633
633
|
else if (eq(b, t)) v = o;
|
|
634
634
|
else {
|
|
635
|
-
const r =
|
|
635
|
+
const r = resolve9?.(k, b, o, t);
|
|
636
636
|
if (r && r.resolved) v = r.value;
|
|
637
637
|
else {
|
|
638
638
|
conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
|
|
@@ -2330,7 +2330,8 @@ function runValidate(loaded) {
|
|
|
2330
2330
|
if (compiled) gameScopes.push(...unplayableScopes(loaded, compiled));
|
|
2331
2331
|
const unresolvedMerges = [...sidecarIssues(tree.get(CONFLICT_SIDECAR)), ...markedShards(SHARD_EXTENSIONS.flatMap((ext) => tree.get(ext)))];
|
|
2332
2332
|
const orphans = orphanShards(loaded, tree);
|
|
2333
|
-
const
|
|
2333
|
+
const extensionPassed = extensionIssues(loaded);
|
|
2334
|
+
const extensions = [...extensionPassed.issues, ...gitattributesHint(loaded)];
|
|
2334
2335
|
const structuralErrors = structural.filter((i) => i.severity === void 0);
|
|
2335
2336
|
const reachability = compiled && structuralErrors.length === 0 && !conditions.some(isError) ? reachabilityIssues(loaded, compiled) : [];
|
|
2336
2337
|
return {
|
|
@@ -2345,6 +2346,7 @@ function runValidate(loaded) {
|
|
|
2345
2346
|
gameScopes,
|
|
2346
2347
|
localisation,
|
|
2347
2348
|
extensions,
|
|
2349
|
+
extensionPools: extensionPassed.pools,
|
|
2348
2350
|
ok: !localisation.some(isError) && !extensions.some(isError) && structuralErrors.length === 0 && !conditions.some(isError) && !interpolation.some(isError) && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0 && orphans.length === 0 && !gameScopes.some(isError)
|
|
2349
2351
|
};
|
|
2350
2352
|
}
|
|
@@ -2360,20 +2362,20 @@ function exportBlockers(loaded) {
|
|
|
2360
2362
|
...structural.map((i) => `[${i.code}] ${i.message}`),
|
|
2361
2363
|
...[...conditions, ...interpolation].map((i) => `[${i.field}] ${i.nodeId}: ${i.message} (${i.src})`),
|
|
2362
2364
|
...locIssues(loaded).filter(isError).map((i) => `[localisation] ${i.file}: ${i.message}`),
|
|
2363
|
-
...extensionIssues(loaded).filter(isError).map((i) => `[extension] ${i.file}: ${i.message}`)
|
|
2365
|
+
...extensionIssues(loaded).issues.filter(isError).map((i) => `[extension] ${i.file}: ${i.message}`)
|
|
2364
2366
|
];
|
|
2365
2367
|
}
|
|
2366
2368
|
function extensionIssues(loaded) {
|
|
2367
2369
|
const loadIssues = loaded.extensionIssues.map((i) => ({ severity: "error", file: i.file, message: i.message }));
|
|
2368
|
-
if (loaded.project.extensions === void 0 && loaded.manifests.length === 0 && loaded.extensionShards.length === 0) return loadIssues;
|
|
2369
|
-
const { issues } = extensionPass({
|
|
2370
|
+
if (loaded.project.extensions === void 0 && loaded.manifests.length === 0 && loaded.extensionShards.length === 0) return { issues: loadIssues, pools: {} };
|
|
2371
|
+
const { issues, pools } = extensionPass({
|
|
2370
2372
|
project: loaded.project,
|
|
2371
2373
|
scenes: loaded.scenes,
|
|
2372
2374
|
...loadedExtensions(loaded),
|
|
2373
2375
|
projectPath: loaded.projectFile,
|
|
2374
2376
|
sceneFiles: loaded.sceneFiles
|
|
2375
2377
|
});
|
|
2376
|
-
return [...loadIssues, ...pinSettingsIssues(loaded), ...issues];
|
|
2378
|
+
return { issues: [...loadIssues, ...pinSettingsIssues(loaded), ...issues], pools };
|
|
2377
2379
|
}
|
|
2378
2380
|
function pinSettingsIssues(loaded) {
|
|
2379
2381
|
return Object.entries(loaded.project.extensions ?? {}).filter(([, pin]) => typeof pin === "object" && pin !== null && Object.prototype.hasOwnProperty.call(pin, "settings")).map(([id]) => ({
|
|
@@ -2633,19 +2635,41 @@ function scanAudioStatus(loaded) {
|
|
|
2633
2635
|
const snap = scanAudio(loaded);
|
|
2634
2636
|
return snap && new Map(Object.entries(snap).map(([id, e]) => [id, e.status]));
|
|
2635
2637
|
}
|
|
2636
|
-
function audioManifest(snapshot, projectRoot, audioRoot) {
|
|
2638
|
+
function audioManifest(snapshot, projectRoot, audioRoot, opts = {}) {
|
|
2637
2639
|
const base = resolve4(projectRoot, audioRoot);
|
|
2638
2640
|
const clips = {};
|
|
2639
2641
|
for (const beatId of Object.keys(snapshot).sort()) {
|
|
2640
2642
|
const entry = snapshot[beatId];
|
|
2643
|
+
if (opts.lines && !opts.lines.has(beatId)) continue;
|
|
2644
|
+
if (opts.leaveOut?.has(entry.status)) continue;
|
|
2641
2645
|
clips[beatId] = { file: relative4(base, entry.path).split(sep3).join("/"), status: entry.status };
|
|
2642
2646
|
}
|
|
2643
2647
|
return JSON.stringify({ schema: AUDIO_MANIFEST_SCHEMA, clips }, null, 2) + "\n";
|
|
2644
2648
|
}
|
|
2645
|
-
function
|
|
2649
|
+
function manifestLineIds(loaded) {
|
|
2650
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2651
|
+
const walk = (n) => {
|
|
2652
|
+
if (Array.isArray(n)) {
|
|
2653
|
+
for (const x of n) walk(x);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (!n || typeof n !== "object") return;
|
|
2657
|
+
const o = n;
|
|
2658
|
+
if (typeof o["id"] === "string" && typeof o["kind"] === "string") ids.add(o["id"]);
|
|
2659
|
+
for (const v of Object.values(o)) if (v && typeof v === "object") walk(v);
|
|
2660
|
+
};
|
|
2661
|
+
for (const scene of loaded.scenes) walk(scene);
|
|
2662
|
+
for (const id of mergeAuthoring2(loaded).cut) ids.delete(id);
|
|
2663
|
+
return ids;
|
|
2664
|
+
}
|
|
2665
|
+
function audioManifestWrite(loaded, snapshot, opts = {}) {
|
|
2646
2666
|
const p = loaded.project;
|
|
2647
2667
|
if (!snapshot || !audioFoldersActive(p) || !Object.keys(snapshot).length) return void 0;
|
|
2648
|
-
|
|
2668
|
+
const leaveOut = opts.leaveOutPlaceholderSpeech && p.placeholderSpeech ? /* @__PURE__ */ new Set([p.placeholderSpeech.status]) : void 0;
|
|
2669
|
+
return {
|
|
2670
|
+
path: opts.path ?? join7(resolve4(loaded.root, p.audioRoot), AUDIO_MANIFEST_FILE),
|
|
2671
|
+
content: audioManifest(snapshot, loaded.root, p.audioRoot, { lines: manifestLineIds(loaded), ...leaveOut ? { leaveOut } : {} })
|
|
2672
|
+
};
|
|
2649
2673
|
}
|
|
2650
2674
|
|
|
2651
2675
|
// src/export.ts
|
|
@@ -2693,7 +2717,10 @@ function planBuild(loaded, opts = {}) {
|
|
|
2693
2717
|
const bundle = runExport(loaded, opts);
|
|
2694
2718
|
const text = canonicalStringify4(bundle, { trailingComma: false });
|
|
2695
2719
|
const scopes = patterScopesWrite(loaded.project, loaded.gameScopes);
|
|
2696
|
-
const audio = audioManifestWrite(loaded, opts.audio ?? scanAudio(loaded)
|
|
2720
|
+
const audio = audioManifestWrite(loaded, opts.audio ?? scanAudio(loaded), {
|
|
2721
|
+
...opts.audioManifestPath ? { path: opts.audioManifestPath } : {},
|
|
2722
|
+
...opts.leaveOutPlaceholderSpeech ? { leaveOutPlaceholderSpeech: true } : {}
|
|
2723
|
+
});
|
|
2697
2724
|
return {
|
|
2698
2725
|
bundle,
|
|
2699
2726
|
text,
|
|
@@ -3075,6 +3102,444 @@ ${PLAYER_JS}`,
|
|
|
3075
3102
|
};
|
|
3076
3103
|
}
|
|
3077
3104
|
|
|
3105
|
+
// src/placeholder-speech.ts
|
|
3106
|
+
import { createHash, createSign } from "crypto";
|
|
3107
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, openSync, readSync, closeSync, fstatSync } from "fs";
|
|
3108
|
+
import { homedir } from "os";
|
|
3109
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative5, resolve as resolve5 } from "path";
|
|
3110
|
+
import { DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES3, deriveRecordingFolders as deriveRecordingFolders2 } from "@patterkit/model";
|
|
3111
|
+
import { stripCaptions } from "@patterkit/dialect";
|
|
3112
|
+
import { textHash, withStamps, readInfoChunks, accentForLanguage, defaultPlaceholderVoice, TEXT_STAMP, GENERATED_STAMP } from "@patterkit/core";
|
|
3113
|
+
|
|
3114
|
+
// src/voice-script.ts
|
|
3115
|
+
import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, DEFAULT_DOCUMENTATION_CLASSES as DEFAULT_DOCUMENTATION_CLASSES3, projectQualifiers as projectQualifiers3 } from "@patterkit/model";
|
|
3116
|
+
function plainVoice(text) {
|
|
3117
|
+
return text.replace(/<\/?(?:b|i|bi)>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
3118
|
+
}
|
|
3119
|
+
function groupLabel(node, source) {
|
|
3120
|
+
if (node.prompt) return plainVoice(source[node.prompt.id]?.trim() || "option");
|
|
3121
|
+
if (node.selector === "choice") return "";
|
|
3122
|
+
return node.selector ?? "group";
|
|
3123
|
+
}
|
|
3124
|
+
function runVoiceScript(loaded, opts = {}) {
|
|
3125
|
+
const { project } = loaded;
|
|
3126
|
+
const everything = !!opts.everything;
|
|
3127
|
+
const ladderDecls = project.writingStatuses ?? DEFAULT_WRITING_STATUSES;
|
|
3128
|
+
const writingLadder = ladderDecls.map((s) => s.name);
|
|
3129
|
+
const recordingLadder = (project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES2).map((s) => s.name);
|
|
3130
|
+
const stub = writingLadder[0];
|
|
3131
|
+
const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
|
|
3132
|
+
const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
|
|
3133
|
+
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring2(loaded);
|
|
3134
|
+
const recordingBase = opts.recordingOverride ?? manualRecordingOf;
|
|
3135
|
+
const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
|
|
3136
|
+
const source = sourceStrings(loaded);
|
|
3137
|
+
const actorOf = /* @__PURE__ */ new Map();
|
|
3138
|
+
for (const c of project.cast ?? []) if (c.actor) actorOf.set(c.name, c.actor);
|
|
3139
|
+
const qualifierName = new Map(projectQualifiers3(project).map((q) => [q.gameId, q.name]));
|
|
3140
|
+
const voClasses = classesForChannel(project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES3, "vo");
|
|
3141
|
+
const ownVo = (id) => (docsOf.get(id) ?? []).filter((d) => d.type !== void 0 && voClasses.has(d.type)).map((d) => d.text);
|
|
3142
|
+
const lines = [];
|
|
3143
|
+
const folders = sceneFolderPaths(loaded);
|
|
3144
|
+
let sceneNow = "";
|
|
3145
|
+
const emitLine = (beat, scope, leadingVo) => {
|
|
3146
|
+
if (beat.kind !== "line" || cutSet.has(beat.id)) return false;
|
|
3147
|
+
const ws = writingOf.get(beat.id) ?? stub;
|
|
3148
|
+
if (!everything && recordThreshold !== -1 && (writingIndex.get(ws) ?? 0) < recordThreshold) return false;
|
|
3149
|
+
const own = ownVo(beat.id);
|
|
3150
|
+
const folder = folders.get(sceneNow);
|
|
3151
|
+
lines.push({
|
|
3152
|
+
...folder ? { folder } : {},
|
|
3153
|
+
scope: scope.join(" \u203A "),
|
|
3154
|
+
id: beat.id,
|
|
3155
|
+
character: beat.character ?? "",
|
|
3156
|
+
...beat.qualifier ? { qualifier: qualifierName.get(beat.qualifier) ?? beat.qualifier } : {},
|
|
3157
|
+
actor: beat.character ? actorOf.get(beat.character) : void 0,
|
|
3158
|
+
...beat.direction?.trim() ? { direction: plainVoice(beat.direction.trim()) } : {},
|
|
3159
|
+
text: plainVoice(source[beat.id] ?? ""),
|
|
3160
|
+
comments: leadingVo ? [...leadingVo, ...own] : own,
|
|
3161
|
+
// first line of the run gets the enclosing context
|
|
3162
|
+
recordingStatus: recordingOf(beat.id)
|
|
3163
|
+
});
|
|
3164
|
+
return true;
|
|
3165
|
+
};
|
|
3166
|
+
const walkNode = (node, scope, ancestorVo) => {
|
|
3167
|
+
if (node.type === "group") {
|
|
3168
|
+
if (cutSet.has(node.id)) return;
|
|
3169
|
+
const label = groupLabel(node, source);
|
|
3170
|
+
const childScope = label ? [...scope, label] : scope;
|
|
3171
|
+
const childVo = [...ancestorVo, ...ownVo(node.id)];
|
|
3172
|
+
if (node.prompt) emitLine(node.prompt, childScope, childVo);
|
|
3173
|
+
for (const child2 of node.children ?? []) walkNode(child2, childScope, childVo);
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
if (cutSet.has(node.id)) return;
|
|
3177
|
+
let leading = true;
|
|
3178
|
+
for (const beat of node.beats ?? []) {
|
|
3179
|
+
if (emitLine(beat, scope, leading ? ancestorVo : void 0)) leading = false;
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
for (const scene of loaded.scenes) {
|
|
3183
|
+
if (cutSet.has(scene.id)) continue;
|
|
3184
|
+
sceneNow = scene.id;
|
|
3185
|
+
const sceneVo = ownVo(scene.id);
|
|
3186
|
+
for (const block of scene.blocks) {
|
|
3187
|
+
if (cutSet.has(block.id)) continue;
|
|
3188
|
+
const scope = [scene.name, block.name].filter(Boolean);
|
|
3189
|
+
const blockVo = [...sceneVo, ...ownVo(block.id)];
|
|
3190
|
+
for (const child2 of block.children ?? []) walkNode(child2, scope, blockVo);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
return { project: project.project.id, voiced: project.voiced ?? false, recordingLadder, everything, lines };
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
// src/placeholder-speech.ts
|
|
3197
|
+
var PLACEHOLDER_SAMPLE_RATE = 16e3;
|
|
3198
|
+
var TRIM_THRESHOLD = 0.01;
|
|
3199
|
+
var TRIM_PAD_SECONDS = 0.05;
|
|
3200
|
+
function resolveKeyFile(projectRoot, keyFile, env = process.env) {
|
|
3201
|
+
const stored = keyFile?.trim();
|
|
3202
|
+
if (stored) {
|
|
3203
|
+
const path = expandKeyPath(projectRoot, stored);
|
|
3204
|
+
if (existsSync5(path)) return { path, source: "project", stored };
|
|
3205
|
+
}
|
|
3206
|
+
const fromEnv = env["GOOGLE_APPLICATION_CREDENTIALS"]?.trim();
|
|
3207
|
+
if (fromEnv && existsSync5(fromEnv)) return { path: fromEnv, source: "environment", ...stored ? { stored } : {} };
|
|
3208
|
+
const gcloud = gcloudCredentialsPath(env);
|
|
3209
|
+
if (gcloud && existsSync5(gcloud)) return { path: gcloud, source: "gcloud", ...stored ? { stored } : {} };
|
|
3210
|
+
return { source: "none", ...stored ? { stored } : {} };
|
|
3211
|
+
}
|
|
3212
|
+
function gcloudCredentialsPath(env = process.env) {
|
|
3213
|
+
const file = "application_default_credentials.json";
|
|
3214
|
+
const config = env["CLOUDSDK_CONFIG"]?.trim();
|
|
3215
|
+
if (config) return join8(config, file);
|
|
3216
|
+
if (process.platform === "win32") return env["APPDATA"] ? join8(env["APPDATA"], "gcloud", file) : void 0;
|
|
3217
|
+
return join8(homedir(), ".config", "gcloud", file);
|
|
3218
|
+
}
|
|
3219
|
+
function expandKeyPath(projectRoot, stored) {
|
|
3220
|
+
const p = stored.trim();
|
|
3221
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) return join8(homedir(), p.slice(2));
|
|
3222
|
+
return isAbsolute3(p) ? p : resolve5(projectRoot, p);
|
|
3223
|
+
}
|
|
3224
|
+
function storedKeyPath(projectRoot, chosen) {
|
|
3225
|
+
const home = homedir();
|
|
3226
|
+
const norm = (s) => s.replace(/\\/g, "/");
|
|
3227
|
+
const c = norm(resolve5(chosen));
|
|
3228
|
+
const h = norm(home);
|
|
3229
|
+
if (c === h || c.startsWith(h + "/")) return "~/" + c.slice(h.length + 1);
|
|
3230
|
+
const rel = relative5(projectRoot, chosen);
|
|
3231
|
+
return norm(isAbsolute3(rel) ? chosen : rel);
|
|
3232
|
+
}
|
|
3233
|
+
function readGoogleKey(path) {
|
|
3234
|
+
let raw;
|
|
3235
|
+
try {
|
|
3236
|
+
raw = JSON.parse(readFileSync7(path, "utf8"));
|
|
3237
|
+
} catch {
|
|
3238
|
+
throw new Error("That file isn't a Google key: it isn't readable JSON.");
|
|
3239
|
+
}
|
|
3240
|
+
const k = raw;
|
|
3241
|
+
const text = (name) => typeof k[name] === "string" ? k[name] : "";
|
|
3242
|
+
if (k["type"] === "service_account") {
|
|
3243
|
+
if (!text("client_email") || !text("private_key")) throw new Error("That key file is missing its account or its private key.");
|
|
3244
|
+
return { kind: "service-account", projectId: text("project_id"), account: text("client_email"), privateKey: text("private_key") };
|
|
3245
|
+
}
|
|
3246
|
+
if (k["type"] === "authorized_user") {
|
|
3247
|
+
if (!text("client_id") || !text("client_secret") || !text("refresh_token")) throw new Error("That gcloud sign-in file is incomplete: sign in again with gcloud auth application-default login.");
|
|
3248
|
+
if (!text("quota_project_id")) throw new Error("That gcloud sign-in names no project to bill: run gcloud auth application-default set-quota-project with your project's id.");
|
|
3249
|
+
return { kind: "user", projectId: text("quota_project_id"), account: text("account"), clientId: text("client_id"), clientSecret: text("client_secret"), refreshToken: text("refresh_token") };
|
|
3250
|
+
}
|
|
3251
|
+
throw new Error("That file isn't a Google key Placeholder Speech can use: choose a service account's JSON key, or gcloud's sign-in (application_default_credentials.json).");
|
|
3252
|
+
}
|
|
3253
|
+
var b64url = (b) => Buffer.from(b).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
3254
|
+
async function mintToken(key, fetchFn = fetch) {
|
|
3255
|
+
if (key.kind === "user") {
|
|
3256
|
+
const res2 = await fetchFn("https://oauth2.googleapis.com/token", {
|
|
3257
|
+
method: "POST",
|
|
3258
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
3259
|
+
body: new URLSearchParams({ grant_type: "refresh_token", client_id: key.clientId, client_secret: key.clientSecret, refresh_token: key.refreshToken }).toString()
|
|
3260
|
+
});
|
|
3261
|
+
if (!res2.ok) throw new SpeechError(res2.status === 400 || res2.status === 401 ? "auth" : kindForStatus(res2.status), `Google refused this sign-in (${res2.status}): ${await errorText(res2)}. Sign in again with gcloud auth application-default login.`);
|
|
3262
|
+
return tokenFrom(await res2.json());
|
|
3263
|
+
}
|
|
3264
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3265
|
+
const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
3266
|
+
const claims = b64url(JSON.stringify({ iss: key.account, scope: "https://www.googleapis.com/auth/cloud-platform", aud: "https://oauth2.googleapis.com/token", iat: now, exp: now + 3600 }));
|
|
3267
|
+
const signer = createSign("RSA-SHA256");
|
|
3268
|
+
signer.update(`${header}.${claims}`);
|
|
3269
|
+
const jwt = `${header}.${claims}.${b64url(signer.sign(key.privateKey))}`;
|
|
3270
|
+
const res = await fetchFn("https://oauth2.googleapis.com/token", {
|
|
3271
|
+
method: "POST",
|
|
3272
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
3273
|
+
body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt }).toString()
|
|
3274
|
+
});
|
|
3275
|
+
if (!res.ok) throw new SpeechError(res.status === 400 || res.status === 401 ? "auth" : kindForStatus(res.status), `Google refused this key (${res.status}): ${await errorText(res)}`);
|
|
3276
|
+
return tokenFrom(await res.json());
|
|
3277
|
+
}
|
|
3278
|
+
function tokenFrom(body) {
|
|
3279
|
+
if (!body.access_token) throw new SpeechError("auth", "Google gave no token for this key.");
|
|
3280
|
+
return { token: body.access_token, expiresAt: Date.now() + (body.expires_in ?? 3600) * 1e3 - 6e4 };
|
|
3281
|
+
}
|
|
3282
|
+
var SpeechError = class extends Error {
|
|
3283
|
+
constructor(kind, message) {
|
|
3284
|
+
super(message);
|
|
3285
|
+
this.kind = kind;
|
|
3286
|
+
}
|
|
3287
|
+
kind;
|
|
3288
|
+
};
|
|
3289
|
+
function kindForStatus(status) {
|
|
3290
|
+
if (status === 401) return "auth";
|
|
3291
|
+
if (status === 403) return "forbidden";
|
|
3292
|
+
if (status === 429) return "rate";
|
|
3293
|
+
if (status >= 500) return "server";
|
|
3294
|
+
return "bad-request";
|
|
3295
|
+
}
|
|
3296
|
+
async function errorText(res) {
|
|
3297
|
+
try {
|
|
3298
|
+
const j = await res.json();
|
|
3299
|
+
if (typeof j.error === "object" && j.error?.message) return j.error.message;
|
|
3300
|
+
return j.error_description ?? (typeof j.error === "string" ? j.error : res.statusText);
|
|
3301
|
+
} catch {
|
|
3302
|
+
return res.statusText;
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function describeSpeechError(e) {
|
|
3306
|
+
if (e instanceof SpeechError) {
|
|
3307
|
+
if (e.kind === "forbidden" && /not been used|disabled|SERVICE_DISABLED/i.test(e.message)) return "Google refused this key: the Text-to-Speech service isn't switched on for its project.";
|
|
3308
|
+
if (e.kind === "forbidden" && /billing/i.test(e.message)) return "Google refused this key: its project has no billing account.";
|
|
3309
|
+
return e.message;
|
|
3310
|
+
}
|
|
3311
|
+
if (e instanceof TypeError || e?.code === "ENOTFOUND") return "Couldn't reach Google: no network connection.";
|
|
3312
|
+
return e instanceof Error ? e.message : String(e);
|
|
3313
|
+
}
|
|
3314
|
+
var isNetworkError = (e) => e instanceof TypeError || ["ENOTFOUND", "ECONNREFUSED", "ETIMEDOUT", "EAI_AGAIN", "ECONNRESET"].includes(e?.code ?? e?.cause?.code ?? "");
|
|
3315
|
+
function requestHash(r) {
|
|
3316
|
+
const shape = { text: r.text, voice: r.voice, languageCode: r.languageCode, encoding: "LINEAR16", sampleRate: PLACEHOLDER_SAMPLE_RATE, trim: TRIM_PAD_SECONDS };
|
|
3317
|
+
return createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 16);
|
|
3318
|
+
}
|
|
3319
|
+
async function synthesize(auth, r, fetchFn = fetch) {
|
|
3320
|
+
const { token, quotaProject } = typeof auth === "string" ? { token: auth, quotaProject: void 0 } : auth;
|
|
3321
|
+
let res;
|
|
3322
|
+
try {
|
|
3323
|
+
res = await fetchFn("https://texttospeech.googleapis.com/v1/text:synthesize", {
|
|
3324
|
+
method: "POST",
|
|
3325
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...quotaProject ? { "x-goog-user-project": quotaProject } : {} },
|
|
3326
|
+
body: JSON.stringify({ input: { text: r.text }, voice: { languageCode: r.languageCode, name: r.voice }, audioConfig: { audioEncoding: "LINEAR16", sampleRateHertz: PLACEHOLDER_SAMPLE_RATE } })
|
|
3327
|
+
});
|
|
3328
|
+
} catch (e) {
|
|
3329
|
+
if (isNetworkError(e)) throw new SpeechError("offline", "Couldn't reach Google: no network connection.");
|
|
3330
|
+
throw e;
|
|
3331
|
+
}
|
|
3332
|
+
if (!res.ok) throw new SpeechError(kindForStatus(res.status), `Google refused the request (${res.status}): ${await errorText(res)}`);
|
|
3333
|
+
const body = await res.json();
|
|
3334
|
+
if (!body.audioContent) throw new SpeechError("server", "Google sent no audio.");
|
|
3335
|
+
return new Uint8Array(Buffer.from(body.audioContent, "base64"));
|
|
3336
|
+
}
|
|
3337
|
+
function finishTake(wav, stamps) {
|
|
3338
|
+
const view = new DataView(wav.buffer, wav.byteOffset, wav.byteLength);
|
|
3339
|
+
const ascii = (at) => String.fromCharCode(wav[at], wav[at + 1], wav[at + 2], wav[at + 3]);
|
|
3340
|
+
let fmtAt = -1, dataAt = -1, dataLen = 0;
|
|
3341
|
+
for (let pos = 12; pos + 8 <= wav.length; ) {
|
|
3342
|
+
const id = ascii(pos), len = view.getUint32(pos + 4, true);
|
|
3343
|
+
if (id === "fmt ") fmtAt = pos + 8;
|
|
3344
|
+
if (id === "data") {
|
|
3345
|
+
dataAt = pos + 8;
|
|
3346
|
+
dataLen = Math.min(len, wav.length - dataAt);
|
|
3347
|
+
}
|
|
3348
|
+
pos += 8 + len + len % 2;
|
|
3349
|
+
}
|
|
3350
|
+
if (fmtAt < 0 || dataAt < 0) throw new SpeechError("server", "Google's audio wasn't a WAV.");
|
|
3351
|
+
const channels = view.getUint16(fmtAt + 2, true), rate = view.getUint32(fmtAt + 4, true), bits = view.getUint16(fmtAt + 14, true);
|
|
3352
|
+
if (bits !== 16) throw new SpeechError("server", "Google's audio wasn't 16-bit.");
|
|
3353
|
+
const frames = Math.floor(dataLen / (2 * channels));
|
|
3354
|
+
const amp = (i) => {
|
|
3355
|
+
let m = 0;
|
|
3356
|
+
for (let c = 0; c < channels; c++) m = Math.max(m, Math.abs(view.getInt16(dataAt + (i * channels + c) * 2, true)) / 32768);
|
|
3357
|
+
return m;
|
|
3358
|
+
};
|
|
3359
|
+
const pad = Math.floor(rate * TRIM_PAD_SECONDS);
|
|
3360
|
+
let start = 0, end = frames - 1;
|
|
3361
|
+
for (let i = 0; i < frames; i++) if (amp(i) > TRIM_THRESHOLD) {
|
|
3362
|
+
start = Math.max(0, i - pad);
|
|
3363
|
+
break;
|
|
3364
|
+
}
|
|
3365
|
+
for (let i = frames - 1; i >= start; i--) if (amp(i) > TRIM_THRESHOLD) {
|
|
3366
|
+
end = Math.min(frames - 1, i + pad);
|
|
3367
|
+
break;
|
|
3368
|
+
}
|
|
3369
|
+
const pcm = wav.subarray(dataAt + start * 2 * channels, dataAt + (end + 1) * 2 * channels);
|
|
3370
|
+
const out = new Uint8Array(44 + pcm.length);
|
|
3371
|
+
const o = new DataView(out.buffer);
|
|
3372
|
+
const put = (at, s) => {
|
|
3373
|
+
for (let i = 0; i < 4; i++) out[at + i] = s.charCodeAt(i);
|
|
3374
|
+
};
|
|
3375
|
+
put(0, "RIFF");
|
|
3376
|
+
o.setUint32(4, 36 + pcm.length, true);
|
|
3377
|
+
put(8, "WAVE");
|
|
3378
|
+
put(12, "fmt ");
|
|
3379
|
+
o.setUint32(16, 16, true);
|
|
3380
|
+
o.setUint16(20, 1, true);
|
|
3381
|
+
o.setUint16(22, channels, true);
|
|
3382
|
+
o.setUint32(24, rate, true);
|
|
3383
|
+
o.setUint32(28, rate * channels * 2, true);
|
|
3384
|
+
o.setUint16(32, channels * 2, true);
|
|
3385
|
+
o.setUint16(34, 16, true);
|
|
3386
|
+
put(36, "data");
|
|
3387
|
+
o.setUint32(40, pcm.length, true);
|
|
3388
|
+
out.set(pcm, 44);
|
|
3389
|
+
return withStamps(out, [[TEXT_STAMP, stamps.text], [GENERATED_STAMP, stamps.request]]);
|
|
3390
|
+
}
|
|
3391
|
+
function readGeneratedStampFromFile(path) {
|
|
3392
|
+
let fd;
|
|
3393
|
+
try {
|
|
3394
|
+
fd = openSync(path, "r");
|
|
3395
|
+
const size = fstatSync(fd).size;
|
|
3396
|
+
const head = Buffer.alloc(12);
|
|
3397
|
+
readSync(fd, head, 0, 12, 0);
|
|
3398
|
+
if (head.toString("ascii", 0, 4) !== "RIFF" || head.toString("ascii", 8, 12) !== "WAVE") return null;
|
|
3399
|
+
const hdr = Buffer.alloc(8);
|
|
3400
|
+
for (let pos = 12; pos + 8 <= size; ) {
|
|
3401
|
+
readSync(fd, hdr, 0, 8, pos);
|
|
3402
|
+
const len = hdr.readUInt32LE(4);
|
|
3403
|
+
if (hdr.toString("ascii", 0, 4) === "LIST" && len <= 64 * 1024) {
|
|
3404
|
+
const list = Buffer.alloc(8 + len);
|
|
3405
|
+
readSync(fd, list, 0, list.length, pos);
|
|
3406
|
+
const info = readInfoChunks(new Uint8Array(Buffer.concat([head, list])));
|
|
3407
|
+
const g = info.get(GENERATED_STAMP) ?? info.get("PSVT");
|
|
3408
|
+
if (g) return g;
|
|
3409
|
+
}
|
|
3410
|
+
pos += 8 + len + len % 2;
|
|
3411
|
+
}
|
|
3412
|
+
return null;
|
|
3413
|
+
} catch {
|
|
3414
|
+
return null;
|
|
3415
|
+
} finally {
|
|
3416
|
+
if (fd !== void 0) closeSync(fd);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
function voiceFor(cast, character, accent) {
|
|
3420
|
+
const m = cast?.find((c) => c.name === character);
|
|
3421
|
+
return m?.placeholderVoice ?? defaultPlaceholderVoice(character, accent, m?.gender);
|
|
3422
|
+
}
|
|
3423
|
+
function placeholderAccentOf(p) {
|
|
3424
|
+
return p.placeholderSpeech?.accent ?? accentForLanguage(p.locales?.default);
|
|
3425
|
+
}
|
|
3426
|
+
function planPlaceholderSpeech(loaded, opts = {}) {
|
|
3427
|
+
const p = loaded.project;
|
|
3428
|
+
const ps = p.placeholderSpeech;
|
|
3429
|
+
if (!ps || !p.audioFolders || !p.audioRoot?.trim()) return void 0;
|
|
3430
|
+
const ladder = p.recordingStatuses ?? DEFAULT_RECORDING_STATUSES3;
|
|
3431
|
+
const rung = deriveRecordingFolders2(p.audioRoot, ladder).find((r) => r.name === ps.status);
|
|
3432
|
+
if (!rung?.folder) return void 0;
|
|
3433
|
+
const folder = resolve5(loaded.root, rung.folder);
|
|
3434
|
+
const rank = new Map(ladder.map((r, i) => [r.name, i]));
|
|
3435
|
+
const myRank = rank.get(ps.status) ?? 0;
|
|
3436
|
+
const accent = placeholderAccentOf(p);
|
|
3437
|
+
const snap = opts.snapshot ?? scanAudio(loaded) ?? {};
|
|
3438
|
+
const caps = { open: p.closedCaptions?.open ?? DEFAULT_CAPTION_DELIMITERS.open, close: p.closedCaptions?.close ?? DEFAULT_CAPTION_DELIMITERS.close };
|
|
3439
|
+
const captionCharacter = p.closedCaptions?.character ?? DEFAULT_CAPTION_CHARACTER;
|
|
3440
|
+
const sceneIds = opts.scene ? /* @__PURE__ */ new Set([opts.scene]) : void 0;
|
|
3441
|
+
const plan = { status: ps.status, folder, accent, tasks: [], skipped: {} };
|
|
3442
|
+
const skip = (why) => {
|
|
3443
|
+
plan.skipped[why] = (plan.skipped[why] ?? 0) + 1;
|
|
3444
|
+
};
|
|
3445
|
+
const script = runVoiceScript(loaded, { everything: true });
|
|
3446
|
+
const inScene = sceneIds ? lineIdsInScenes(loaded, sceneIds) : void 0;
|
|
3447
|
+
for (const line of script.lines) {
|
|
3448
|
+
if (opts.only && !opts.only.has(line.id)) continue;
|
|
3449
|
+
if (inScene && !inScene.has(line.id)) continue;
|
|
3450
|
+
const text = stripCaptions(line.text, caps.open, caps.close).replace(/\s+/g, " ").trim();
|
|
3451
|
+
if (!text || line.character === captionCharacter) {
|
|
3452
|
+
skip("unspoken");
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
const have = snap[line.id];
|
|
3456
|
+
if (have && (rank.get(have.status) ?? 0) > myRank) {
|
|
3457
|
+
skip("better");
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
const voice = voiceFor(p.cast, line.character, accent);
|
|
3461
|
+
if (!voice) {
|
|
3462
|
+
skip("no-voice");
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
const request = { text, voice, languageCode: voice.split("-").slice(0, 2).join("-") };
|
|
3466
|
+
const hash = requestHash(request);
|
|
3467
|
+
const forced = opts.force === "all" || opts.force?.has(line.id);
|
|
3468
|
+
if (have && have.status === ps.status) {
|
|
3469
|
+
const stamp = have.generated ?? readGeneratedStampFromFile(have.path);
|
|
3470
|
+
if (!stamp) {
|
|
3471
|
+
skip("not-ours");
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
if (stamp === hash && !forced) {
|
|
3475
|
+
skip("unchanged");
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
plan.tasks.push({ beatId: line.id, character: line.character, request, hash, textHash: textHash(line.text), path: join8(folder, `${line.id}.wav`) });
|
|
3480
|
+
}
|
|
3481
|
+
return plan;
|
|
3482
|
+
}
|
|
3483
|
+
function lineIdsInScenes(loaded, scenes) {
|
|
3484
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3485
|
+
const walk = (node) => {
|
|
3486
|
+
if (!node || typeof node !== "object") return;
|
|
3487
|
+
const n = node;
|
|
3488
|
+
if (typeof n.id === "string") ids.add(n.id);
|
|
3489
|
+
for (const v of Object.values(node)) if (v && typeof v === "object") walk(v);
|
|
3490
|
+
};
|
|
3491
|
+
for (const s of loaded.scenes) if (scenes.has(s.id)) walk(s);
|
|
3492
|
+
return ids;
|
|
3493
|
+
}
|
|
3494
|
+
async function runPlaceholderSpeech(tasks, o) {
|
|
3495
|
+
const result = { made: [], failed: [], skippedLocked: [], notTried: 0 };
|
|
3496
|
+
let next = 0, done = 0;
|
|
3497
|
+
const worker = async () => {
|
|
3498
|
+
while (next < tasks.length && !result.stopped && !o.signal?.aborted) {
|
|
3499
|
+
const t = tasks[next++];
|
|
3500
|
+
let wait = 2e3;
|
|
3501
|
+
for (let attempt = 0; ; attempt++) {
|
|
3502
|
+
try {
|
|
3503
|
+
const raw = await synthesize(await o.token(), t.request, o.fetchFn);
|
|
3504
|
+
const bytes = finishTake(raw, { text: t.textHash, request: t.hash });
|
|
3505
|
+
if (await o.write(t.path, bytes)) result.made.push(t.beatId);
|
|
3506
|
+
else result.skippedLocked.push(t.beatId);
|
|
3507
|
+
break;
|
|
3508
|
+
} catch (e) {
|
|
3509
|
+
const kind = e instanceof SpeechError ? e.kind : isNetworkError(e) ? "offline" : void 0;
|
|
3510
|
+
if (kind === "rate" && attempt < 4) {
|
|
3511
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
3512
|
+
wait *= 2;
|
|
3513
|
+
continue;
|
|
3514
|
+
}
|
|
3515
|
+
if (kind === "auth" || kind === "forbidden" || kind === "offline" || kind === "rate") {
|
|
3516
|
+
result.stopped ??= { kind, message: describeSpeechError(e) };
|
|
3517
|
+
} else result.failed.push({ beatId: t.beatId, error: describeSpeechError(e) });
|
|
3518
|
+
break;
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
done++;
|
|
3522
|
+
o.onProgress?.(done, tasks.length, t);
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
await Promise.all(Array.from({ length: Math.max(1, o.concurrency ?? 2) }, () => worker()));
|
|
3526
|
+
result.notTried = tasks.length - done;
|
|
3527
|
+
return result;
|
|
3528
|
+
}
|
|
3529
|
+
function tokenSource(key, fetchFn) {
|
|
3530
|
+
let cached;
|
|
3531
|
+
let pending;
|
|
3532
|
+
const auth = (token) => key.kind === "user" ? { token, quotaProject: key.projectId } : { token };
|
|
3533
|
+
return async () => {
|
|
3534
|
+
if (cached && cached.expiresAt > Date.now()) return auth(cached.token);
|
|
3535
|
+
pending ??= mintToken(key, fetchFn).finally(() => {
|
|
3536
|
+
pending = void 0;
|
|
3537
|
+
});
|
|
3538
|
+
cached = await pending;
|
|
3539
|
+
return auth(cached.token);
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3078
3543
|
// src/play.ts
|
|
3079
3544
|
import { Engine } from "@patterkit/runtime";
|
|
3080
3545
|
function runPlay(loaded, opts = {}) {
|
|
@@ -3676,23 +4141,23 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
|
|
|
3676
4141
|
}
|
|
3677
4142
|
|
|
3678
4143
|
// src/format.ts
|
|
3679
|
-
import { readFileSync as
|
|
3680
|
-
import { dirname as dirname7, resolve as
|
|
4144
|
+
import { readFileSync as readFileSync9, statSync as statSync3 } from "fs";
|
|
4145
|
+
import { dirname as dirname7, resolve as resolve6 } from "path";
|
|
3681
4146
|
import { canonicalStringify as canonicalStringify7, parseSource as parseSource6 } from "@patterkit/core";
|
|
3682
4147
|
|
|
3683
4148
|
// src/extensions.ts
|
|
3684
|
-
import { readFileSync as
|
|
3685
|
-
import { join as
|
|
4149
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
4150
|
+
import { join as join9, relative as relative6, sep as sep4 } from "path";
|
|
3686
4151
|
import { canonicalStringify as canonicalStringify6, parseSource as parseSource5 } from "@patterkit/core";
|
|
3687
4152
|
import { EXTENSIONS_DIR as EXTENSIONS_DIR3, MANIFEST_EXTENSION as MANIFEST_EXTENSION3 } from "@patterkit/model";
|
|
3688
4153
|
import { manifestProblems, parseManifest, renameStages, targetOf } from "@wildwinter/toolkit/extensions";
|
|
3689
4154
|
var CONFLICT_MARKERS = /^(<{7}|={7}|>{7})( |$)/m;
|
|
3690
|
-
var manifestPath = (projectDir, id) =>
|
|
4155
|
+
var manifestPath = (projectDir, id) => join9(projectDir, EXTENSIONS_DIR3, `${id}${MANIFEST_EXTENSION3}`);
|
|
3691
4156
|
function readProjectManifest(projectDir, id) {
|
|
3692
4157
|
if (!/^[a-z][a-z0-9-]*$/.test(id)) return void 0;
|
|
3693
4158
|
let text;
|
|
3694
4159
|
try {
|
|
3695
|
-
text =
|
|
4160
|
+
text = readFileSync8(manifestPath(projectDir, id), "utf8");
|
|
3696
4161
|
} catch {
|
|
3697
4162
|
return void 0;
|
|
3698
4163
|
}
|
|
@@ -3718,7 +4183,7 @@ function planExtensionUpgrades(loaded) {
|
|
|
3718
4183
|
};
|
|
3719
4184
|
const taken = loaded.project.extensions ?? {};
|
|
3720
4185
|
const moved = {};
|
|
3721
|
-
const rel = (path) =>
|
|
4186
|
+
const rel = (path) => relative6(loaded.root, path).split(sep4).join("/");
|
|
3722
4187
|
for (const [id, pin] of Object.entries(taken)) {
|
|
3723
4188
|
const file = loaded.manifests.find((m) => m.id === id);
|
|
3724
4189
|
const manifest = file !== void 0 ? soundOf(file.manifest) : void 0;
|
|
@@ -3755,7 +4220,7 @@ function planExtensionUpgrades(loaded) {
|
|
|
3755
4220
|
});
|
|
3756
4221
|
}
|
|
3757
4222
|
if (Object.keys(moved).length > 0) {
|
|
3758
|
-
const project = parseSource5(
|
|
4223
|
+
const project = parseSource5(readFileSync8(loaded.projectFile, "utf8"));
|
|
3759
4224
|
writes.set(loaded.projectFile, canonicalStringify6({ ...project, extensions: { ...taken, ...moved } }));
|
|
3760
4225
|
}
|
|
3761
4226
|
return { writes, report, notes };
|
|
@@ -3863,8 +4328,8 @@ function upgradesFor(paths) {
|
|
|
3863
4328
|
continue;
|
|
3864
4329
|
}
|
|
3865
4330
|
const plan = planExtensionUpgrades(loaded);
|
|
3866
|
-
for (const [file, text] of plan.writes) content.set(
|
|
3867
|
-
for (const [file, lines] of plan.notes) notes.set(
|
|
4331
|
+
for (const [file, text] of plan.writes) content.set(resolve6(file), text);
|
|
4332
|
+
for (const [file, lines] of plan.notes) notes.set(resolve6(file), [...notes.get(resolve6(file)) ?? [], ...lines]);
|
|
3868
4333
|
}
|
|
3869
4334
|
return { content, notes };
|
|
3870
4335
|
}
|
|
@@ -3872,11 +4337,11 @@ function runFormat(paths) {
|
|
|
3872
4337
|
const upgrades = upgradesFor(paths);
|
|
3873
4338
|
return paths.flatMap(expand).map((file) => {
|
|
3874
4339
|
if (!isShard(file)) return { file, changed: false, skipped: true };
|
|
3875
|
-
const original =
|
|
3876
|
-
const notes = upgrades.notes.get(
|
|
4340
|
+
const original = readFileSync9(file, "utf8");
|
|
4341
|
+
const notes = upgrades.notes.get(resolve6(file));
|
|
3877
4342
|
let canonical;
|
|
3878
4343
|
try {
|
|
3879
|
-
canonical = upgrades.content.get(
|
|
4344
|
+
canonical = upgrades.content.get(resolve6(file)) ?? canonicalStringify7(parseSource6(original));
|
|
3880
4345
|
} catch (e) {
|
|
3881
4346
|
throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
3882
4347
|
}
|
|
@@ -3886,7 +4351,7 @@ function runFormat(paths) {
|
|
|
3886
4351
|
}
|
|
3887
4352
|
|
|
3888
4353
|
// src/report.ts
|
|
3889
|
-
import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as
|
|
4354
|
+
import { DEFAULT_WRITING_STATUSES as DEFAULT_WRITING_STATUSES2, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES4, RERECORD_STATUS as RERECORD_STATUS2, textIsAction as textIsAction2 } from "@patterkit/model";
|
|
3890
4355
|
var DEFAULT_WORDS_PER_LINE = 6;
|
|
3891
4356
|
var UNATTRIBUTED = "(unattributed)";
|
|
3892
4357
|
function allocateLargestRemainder(total, weights) {
|
|
@@ -3904,10 +4369,10 @@ function allocateLargestRemainder(total, weights) {
|
|
|
3904
4369
|
}
|
|
3905
4370
|
function runReport(loaded, recordingOverride) {
|
|
3906
4371
|
const { project } = loaded;
|
|
3907
|
-
const writingLadder = (project.writingStatuses ??
|
|
3908
|
-
const recordingLadder = (project.recordingStatuses ??
|
|
4372
|
+
const writingLadder = (project.writingStatuses ?? DEFAULT_WRITING_STATUSES2).map((s) => s.name);
|
|
4373
|
+
const recordingLadder = (project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES4).map((s) => s.name);
|
|
3909
4374
|
const stub = writingLadder[0];
|
|
3910
|
-
const ladderDecls = project.writingStatuses ??
|
|
4375
|
+
const ladderDecls = project.writingStatuses ?? DEFAULT_WRITING_STATUSES2;
|
|
3911
4376
|
const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
|
|
3912
4377
|
const shipThreshold = ladderDecls.findIndex((s) => s.readyToShip);
|
|
3913
4378
|
const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
|
|
@@ -4104,6 +4569,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
4104
4569
|
recordingTracked: (project.voiced ?? false) && (project.trackAudioStatus ?? false),
|
|
4105
4570
|
writingLadder,
|
|
4106
4571
|
recordingLadder: reportedRecordingLadder,
|
|
4572
|
+
...project.placeholderSpeech ? { toRecord: [recordingLadder[0], project.placeholderSpeech.status, RERECORD_STATUS2].reduce((n, st, i, all) => all.indexOf(st) === i ? n + (totals.voiced.byRecording[st] ?? 0) : n, 0) } : {},
|
|
4107
4573
|
scenes: reports,
|
|
4108
4574
|
characters: [...characters.values()].sort((a, b) => b.lines + b.estimatedLines - (a.lines + a.estimatedLines)),
|
|
4109
4575
|
locales,
|
|
@@ -4192,6 +4658,7 @@ function renderReportText(data) {
|
|
|
4192
4658
|
out.push(`scene status: ${Object.entries(data.scenesByStatus).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
4193
4659
|
if (data.voiced) out.push(`ready to record: ${t.voiced.readyToRecord} / ready to ship: ${t.voiced.readyToShip} (voiced)`);
|
|
4194
4660
|
if (data.recordingTracked) out.push(`recording: ${Object.entries(t.voiced.byRecording).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
4661
|
+
if (data.recordingTracked && data.toRecord !== void 0) out.push(`still to record: ${data.toRecord} (counting lines with only placeholder speech)`);
|
|
4195
4662
|
if (data.estimating) {
|
|
4196
4663
|
const estLines = data.scenes.reduce((n, s) => n + (s.estimated ? s.estimate ?? 0 : 0), 0);
|
|
4197
4664
|
out.push(`estimating: ${data.coverage.estimated}/${data.coverage.totalScenes} scene(s) estimated (${estLines} lines)`);
|
|
@@ -4574,88 +5041,6 @@ async function xlsxToCatalog(buffer) {
|
|
|
4574
5041
|
return { project, defaultLocale, locale, entries };
|
|
4575
5042
|
}
|
|
4576
5043
|
|
|
4577
|
-
// src/voice-script.ts
|
|
4578
|
-
import { DEFAULT_WRITING_STATUSES as DEFAULT_WRITING_STATUSES2, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES3, DEFAULT_DOCUMENTATION_CLASSES as DEFAULT_DOCUMENTATION_CLASSES3, projectQualifiers as projectQualifiers3 } from "@patterkit/model";
|
|
4579
|
-
function plainVoice(text) {
|
|
4580
|
-
return text.replace(/<\/?(?:b|i|bi)>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
4581
|
-
}
|
|
4582
|
-
function groupLabel(node, source) {
|
|
4583
|
-
if (node.prompt) return plainVoice(source[node.prompt.id]?.trim() || "option");
|
|
4584
|
-
if (node.selector === "choice") return "";
|
|
4585
|
-
return node.selector ?? "group";
|
|
4586
|
-
}
|
|
4587
|
-
function runVoiceScript(loaded, opts = {}) {
|
|
4588
|
-
const { project } = loaded;
|
|
4589
|
-
const everything = !!opts.everything;
|
|
4590
|
-
const ladderDecls = project.writingStatuses ?? DEFAULT_WRITING_STATUSES2;
|
|
4591
|
-
const writingLadder = ladderDecls.map((s) => s.name);
|
|
4592
|
-
const recordingLadder = (project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES3).map((s) => s.name);
|
|
4593
|
-
const stub = writingLadder[0];
|
|
4594
|
-
const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
|
|
4595
|
-
const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
|
|
4596
|
-
const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring2(loaded);
|
|
4597
|
-
const recordingBase = opts.recordingOverride ?? manualRecordingOf;
|
|
4598
|
-
const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
|
|
4599
|
-
const source = sourceStrings(loaded);
|
|
4600
|
-
const actorOf = /* @__PURE__ */ new Map();
|
|
4601
|
-
for (const c of project.cast ?? []) if (c.actor) actorOf.set(c.name, c.actor);
|
|
4602
|
-
const qualifierName = new Map(projectQualifiers3(project).map((q) => [q.gameId, q.name]));
|
|
4603
|
-
const voClasses = classesForChannel(project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES3, "vo");
|
|
4604
|
-
const ownVo = (id) => (docsOf.get(id) ?? []).filter((d) => d.type !== void 0 && voClasses.has(d.type)).map((d) => d.text);
|
|
4605
|
-
const lines = [];
|
|
4606
|
-
const folders = sceneFolderPaths(loaded);
|
|
4607
|
-
let sceneNow = "";
|
|
4608
|
-
const emitLine = (beat, scope, leadingVo) => {
|
|
4609
|
-
if (beat.kind !== "line" || cutSet.has(beat.id)) return false;
|
|
4610
|
-
const ws = writingOf.get(beat.id) ?? stub;
|
|
4611
|
-
if (!everything && recordThreshold !== -1 && (writingIndex.get(ws) ?? 0) < recordThreshold) return false;
|
|
4612
|
-
const own = ownVo(beat.id);
|
|
4613
|
-
const folder = folders.get(sceneNow);
|
|
4614
|
-
lines.push({
|
|
4615
|
-
...folder ? { folder } : {},
|
|
4616
|
-
scope: scope.join(" \u203A "),
|
|
4617
|
-
id: beat.id,
|
|
4618
|
-
character: beat.character ?? "",
|
|
4619
|
-
...beat.qualifier ? { qualifier: qualifierName.get(beat.qualifier) ?? beat.qualifier } : {},
|
|
4620
|
-
actor: beat.character ? actorOf.get(beat.character) : void 0,
|
|
4621
|
-
...beat.direction?.trim() ? { direction: plainVoice(beat.direction.trim()) } : {},
|
|
4622
|
-
text: plainVoice(source[beat.id] ?? ""),
|
|
4623
|
-
comments: leadingVo ? [...leadingVo, ...own] : own,
|
|
4624
|
-
// first line of the run gets the enclosing context
|
|
4625
|
-
recordingStatus: recordingOf(beat.id)
|
|
4626
|
-
});
|
|
4627
|
-
return true;
|
|
4628
|
-
};
|
|
4629
|
-
const walkNode = (node, scope, ancestorVo) => {
|
|
4630
|
-
if (node.type === "group") {
|
|
4631
|
-
if (cutSet.has(node.id)) return;
|
|
4632
|
-
const label = groupLabel(node, source);
|
|
4633
|
-
const childScope = label ? [...scope, label] : scope;
|
|
4634
|
-
const childVo = [...ancestorVo, ...ownVo(node.id)];
|
|
4635
|
-
if (node.prompt) emitLine(node.prompt, childScope, childVo);
|
|
4636
|
-
for (const child2 of node.children ?? []) walkNode(child2, childScope, childVo);
|
|
4637
|
-
return;
|
|
4638
|
-
}
|
|
4639
|
-
if (cutSet.has(node.id)) return;
|
|
4640
|
-
let leading = true;
|
|
4641
|
-
for (const beat of node.beats ?? []) {
|
|
4642
|
-
if (emitLine(beat, scope, leading ? ancestorVo : void 0)) leading = false;
|
|
4643
|
-
}
|
|
4644
|
-
};
|
|
4645
|
-
for (const scene of loaded.scenes) {
|
|
4646
|
-
if (cutSet.has(scene.id)) continue;
|
|
4647
|
-
sceneNow = scene.id;
|
|
4648
|
-
const sceneVo = ownVo(scene.id);
|
|
4649
|
-
for (const block of scene.blocks) {
|
|
4650
|
-
if (cutSet.has(block.id)) continue;
|
|
4651
|
-
const scope = [scene.name, block.name].filter(Boolean);
|
|
4652
|
-
const blockVo = [...sceneVo, ...ownVo(block.id)];
|
|
4653
|
-
for (const child2 of block.children ?? []) walkNode(child2, scope, blockVo);
|
|
4654
|
-
}
|
|
4655
|
-
}
|
|
4656
|
-
return { project: project.project.id, voiced: project.voiced ?? false, recordingLadder, everything, lines };
|
|
4657
|
-
}
|
|
4658
|
-
|
|
4659
5044
|
// src/voice-script-xlsx.ts
|
|
4660
5045
|
async function voiceScriptToXlsx(data) {
|
|
4661
5046
|
const { default: ExcelJS } = await import("exceljs");
|
|
@@ -4786,9 +5171,9 @@ async function scriptToDocx(doc) {
|
|
|
4786
5171
|
import PDFDocument from "pdfkit";
|
|
4787
5172
|
|
|
4788
5173
|
// src/script-fonts.ts
|
|
4789
|
-
import { existsSync as
|
|
5174
|
+
import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
4790
5175
|
import { createRequire } from "module";
|
|
4791
|
-
import { join as
|
|
5176
|
+
import { join as join10 } from "path";
|
|
4792
5177
|
|
|
4793
5178
|
// src/script-fonts.json
|
|
4794
5179
|
var script_fonts_default = {
|
|
@@ -4875,18 +5260,18 @@ function useScriptFontFiles(paths, licencePaths = {}) {
|
|
|
4875
5260
|
cache.clear();
|
|
4876
5261
|
}
|
|
4877
5262
|
function scriptFontLicencePath(pkg) {
|
|
4878
|
-
const dir = (require2.resolve.paths(pkg) ?? []).map((p) =>
|
|
5263
|
+
const dir = (require2.resolve.paths(pkg) ?? []).map((p) => join10(p, pkg)).find((d) => existsSync6(d));
|
|
4879
5264
|
const file = dir && readdirSync5(dir).find((f) => /^(licen[cs]e|ofl)/i.test(f));
|
|
4880
5265
|
if (!dir || !file) throw new Error(`no licence file found for ${pkg}`);
|
|
4881
|
-
return
|
|
5266
|
+
return join10(dir, file);
|
|
4882
5267
|
}
|
|
4883
5268
|
function scriptFontLicences() {
|
|
4884
|
-
return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text:
|
|
5269
|
+
return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text: readFileSync10(licences[pkg] ?? scriptFontLicencePath(pkg), "utf8") }));
|
|
4885
5270
|
}
|
|
4886
5271
|
function scriptFontFiles(face) {
|
|
4887
5272
|
return script_fonts_default[face].map((spec) => {
|
|
4888
5273
|
let bytes = cache.get(spec);
|
|
4889
|
-
if (!bytes) cache.set(spec, bytes =
|
|
5274
|
+
if (!bytes) cache.set(spec, bytes = readFileSync10(located[spec] ?? require2.resolve(spec)));
|
|
4890
5275
|
return bytes;
|
|
4891
5276
|
});
|
|
4892
5277
|
}
|
|
@@ -4926,7 +5311,7 @@ var ROLES = {
|
|
|
4926
5311
|
"Mono": "mono"
|
|
4927
5312
|
};
|
|
4928
5313
|
async function scriptToPdf(doc) {
|
|
4929
|
-
return new Promise((
|
|
5314
|
+
return new Promise((resolve9, reject) => {
|
|
4930
5315
|
const pdf = new PDFDocument({ size: "A4", margins: { top: MARGIN, bottom: MARGIN, left: MARGIN, right: MARGIN }, info: { Title: doc.project, Author: "Patter" }, font: null });
|
|
4931
5316
|
const register = (prefix, face, firstIsPrefix) => scriptFontFiles(face).map((bytes, i) => {
|
|
4932
5317
|
const name = firstIsPrefix && i === 0 ? prefix : `${prefix}~${i}`;
|
|
@@ -4970,7 +5355,7 @@ async function scriptToPdf(doc) {
|
|
|
4970
5355
|
const bodyPieces = (runs, color, base = {}) => runs.flatMap((r) => r.code ? split(r.text, "Mono", ACCENT) : split(r.text, SERIF2(base.bold || r.bold, base.italic || r.italic), color));
|
|
4971
5356
|
const chunks = [];
|
|
4972
5357
|
pdf.on("data", (c) => chunks.push(c));
|
|
4973
|
-
pdf.on("end", () =>
|
|
5358
|
+
pdf.on("end", () => resolve9(Buffer.concat(chunks)));
|
|
4974
5359
|
pdf.on("error", reject);
|
|
4975
5360
|
const pageRight = pdf.page.width - MARGIN;
|
|
4976
5361
|
const pageBottom = pdf.page.height - MARGIN;
|
|
@@ -5128,8 +5513,8 @@ async function scriptToPdf(doc) {
|
|
|
5128
5513
|
}
|
|
5129
5514
|
|
|
5130
5515
|
// src/init.ts
|
|
5131
|
-
import { join as
|
|
5132
|
-
import { readdirSync as readdirSync6, existsSync as
|
|
5516
|
+
import { join as join11, basename as basename6, resolve as resolve7 } from "path";
|
|
5517
|
+
import { readdirSync as readdirSync6, existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
|
|
5133
5518
|
import { newId, slug, canonicalStringify as canonicalStringify8 } from "@patterkit/core";
|
|
5134
5519
|
import { FLOW_SCHEMA, PROJECT_SCHEMA, STRINGS_SCHEMA as STRINGS_SCHEMA2, MANIFEST_EXTENSION as MANIFEST_EXTENSION4, EXTENSIONS_DIR as EXTENSIONS_DIR4 } from "@patterkit/model";
|
|
5135
5520
|
import { vcsConfigFiles, bundlePostureOf } from "@wildwinter/toolkit/vcs";
|
|
@@ -5144,7 +5529,7 @@ var PATTER_VCS_FORMAT = {
|
|
|
5144
5529
|
conflict: CONFLICT_SIDECAR.slice(1)
|
|
5145
5530
|
};
|
|
5146
5531
|
function runInit(opts) {
|
|
5147
|
-
const dir =
|
|
5532
|
+
const dir = resolve7(opts.dir);
|
|
5148
5533
|
const name = opts.name?.trim() || basename6(dir).replace(/\.patter$/, "");
|
|
5149
5534
|
const fileSlug = slug(name);
|
|
5150
5535
|
let existing = [];
|
|
@@ -5195,27 +5580,27 @@ function runInit(opts) {
|
|
|
5195
5580
|
strings: { [beatId]: `Welcome to ${name}. This is the first line of your story - replace it with your own.` }
|
|
5196
5581
|
};
|
|
5197
5582
|
const bundle = opts.bundle ?? "commit";
|
|
5198
|
-
const projectFile =
|
|
5583
|
+
const projectFile = join11(dir, `${fileSlug}.patterproj`);
|
|
5199
5584
|
const writes = [
|
|
5200
|
-
{ path:
|
|
5201
|
-
{ path:
|
|
5202
|
-
{ path:
|
|
5585
|
+
{ path: join11(dir, "scenes", "start.patterflow"), content: canonicalStringify8(flow) },
|
|
5586
|
+
{ path: join11(dir, "loc", "en", "start.patterloc"), content: canonicalStringify8(locale) },
|
|
5587
|
+
{ path: join11(dir, ".editorconfig"), content: EDITORCONFIG },
|
|
5203
5588
|
...vcsConfigWrites(dir, opts.vcs, bundle),
|
|
5204
5589
|
{ path: projectFile, content: canonicalStringify8(project) }
|
|
5205
5590
|
];
|
|
5206
|
-
const collisions = writes.map((w) => w.path).filter((p) =>
|
|
5591
|
+
const collisions = writes.map((w) => w.path).filter((p) => existsSync7(p));
|
|
5207
5592
|
if (collisions.length > 0) {
|
|
5208
5593
|
throw new Error(`refusing to overwrite existing file(s): ${collisions.join(", ")}`);
|
|
5209
5594
|
}
|
|
5210
5595
|
return { writes, projectFile, name };
|
|
5211
5596
|
}
|
|
5212
5597
|
function vcsConfigWrites(dir, vcs, bundle = "commit") {
|
|
5213
|
-
return vcsConfigFiles(PATTER_VCS_FORMAT, vcs, bundle).map((f) => ({ path:
|
|
5598
|
+
return vcsConfigFiles(PATTER_VCS_FORMAT, vcs, bundle).map((f) => ({ path: join11(dir, f.path), content: f.content }));
|
|
5214
5599
|
}
|
|
5215
5600
|
function currentBundlePosture(dir, vcs) {
|
|
5216
5601
|
return bundlePostureOf(PATTER_VCS_FORMAT, vcs, (name) => {
|
|
5217
5602
|
try {
|
|
5218
|
-
return
|
|
5603
|
+
return readFileSync11(join11(dir, name), "utf8");
|
|
5219
5604
|
} catch {
|
|
5220
5605
|
return void 0;
|
|
5221
5606
|
}
|
|
@@ -5235,7 +5620,7 @@ indent_size = 2
|
|
|
5235
5620
|
`;
|
|
5236
5621
|
|
|
5237
5622
|
// src/resolve.ts
|
|
5238
|
-
import { walkNodes as walkNodes4, effectiveGameId as effectiveGameId2, DEFAULT_WRITING_STATUSES as DEFAULT_WRITING_STATUSES3, DEFAULT_RECORDING_STATUSES as
|
|
5623
|
+
import { walkNodes as walkNodes4, effectiveGameId as effectiveGameId2, DEFAULT_WRITING_STATUSES as DEFAULT_WRITING_STATUSES3, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES5 } from "@patterkit/model";
|
|
5239
5624
|
import { defaultFor } from "@wildwinter/scoperegistry";
|
|
5240
5625
|
import { parseFieldQuery, fieldNameMatches, fieldValueMatches, fieldValueText } from "@wildwinter/toolkit";
|
|
5241
5626
|
function runResolve(loaded, query) {
|
|
@@ -5277,7 +5662,7 @@ function runStatusBrowse(loaded, status, dimension = "writing", focus, recording
|
|
|
5277
5662
|
const merged = mergeAuthoring2(loaded);
|
|
5278
5663
|
const recording = dimension === "recording";
|
|
5279
5664
|
const statusOf = recording ? recordingOverride ?? merged.recording : merged.writing;
|
|
5280
|
-
const ladder = recording ? loaded.project.recordingStatuses ??
|
|
5665
|
+
const ladder = recording ? loaded.project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES5 : loaded.project.writingStatuses ?? DEFAULT_WRITING_STATUSES3;
|
|
5281
5666
|
const lowest = ladder[0]?.name;
|
|
5282
5667
|
const statusAt = (id) => recording ? effectiveRecording(id, statusOf, merged.rerecord, lowest ?? "") : statusOf.get(id) ?? lowest;
|
|
5283
5668
|
const out = [];
|
|
@@ -5655,7 +6040,7 @@ function runReplace(loaded, opts) {
|
|
|
5655
6040
|
}
|
|
5656
6041
|
|
|
5657
6042
|
// src/pin.ts
|
|
5658
|
-
import { readFileSync as
|
|
6043
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5659
6044
|
import { canonicalStringify as canonicalStringify10, parseSource as parseSource7 } from "@patterkit/core";
|
|
5660
6045
|
import { effectiveGameId as effectiveGameId3, gameIdify } from "@patterkit/model";
|
|
5661
6046
|
function toPin(node) {
|
|
@@ -5683,7 +6068,7 @@ function planPins(loaded) {
|
|
|
5683
6068
|
});
|
|
5684
6069
|
if (!changed) continue;
|
|
5685
6070
|
const next = { ...scene, ...sceneGameId !== void 0 ? { gameId: sceneGameId } : {}, blocks };
|
|
5686
|
-
const file = parseSource7(
|
|
6071
|
+
const file = parseSource7(readFileSync12(path, "utf8"));
|
|
5687
6072
|
plan.writes.push({ path, content: canonicalStringify10({ ...file, scene: next }) });
|
|
5688
6073
|
plan.scenes.push(next);
|
|
5689
6074
|
}
|
|
@@ -5692,8 +6077,8 @@ function planPins(loaded) {
|
|
|
5692
6077
|
|
|
5693
6078
|
// src/unpack.ts
|
|
5694
6079
|
import JSZip2 from "jszip";
|
|
5695
|
-
import { join as
|
|
5696
|
-
import { existsSync as
|
|
6080
|
+
import { join as join12, resolve as resolve8, sep as sep5 } from "path";
|
|
6081
|
+
import { existsSync as existsSync8, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
5697
6082
|
import { parseSource as parseSource8, canonicalStringify as canonicalStringify11 } from "@patterkit/core";
|
|
5698
6083
|
import { isUnsafeEntry } from "@wildwinter/toolkit/archive";
|
|
5699
6084
|
import { SCOPES_FILE_SUFFIX as SCOPES_FILE_SUFFIX2 } from "@wildwinter/scoperegistry/scopes";
|
|
@@ -5767,7 +6152,7 @@ function projectIdIn(dir) {
|
|
|
5767
6152
|
}
|
|
5768
6153
|
for (const n of names) {
|
|
5769
6154
|
try {
|
|
5770
|
-
const id = parseSource8(
|
|
6155
|
+
const id = parseSource8(readFileSync13(join12(dir, n), "utf8")).project?.id;
|
|
5771
6156
|
if (id) return id;
|
|
5772
6157
|
} catch {
|
|
5773
6158
|
}
|
|
@@ -5794,7 +6179,7 @@ function checkProvenance(returned, base, target) {
|
|
|
5794
6179
|
}
|
|
5795
6180
|
function targetProjectId(projectDir) {
|
|
5796
6181
|
try {
|
|
5797
|
-
const pf = parseSource8(
|
|
6182
|
+
const pf = parseSource8(readFileSync13(findProjectFile(projectDir), "utf8"));
|
|
5798
6183
|
return pf.project?.id;
|
|
5799
6184
|
} catch {
|
|
5800
6185
|
return void 0;
|
|
@@ -5817,13 +6202,13 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5817
6202
|
for (const [rel, theirText] of [...theirs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
5818
6203
|
const outPath = containedWrite(projectDir, rel);
|
|
5819
6204
|
const baseText = base.get(rel);
|
|
5820
|
-
if (!
|
|
6205
|
+
if (!existsSync8(outPath)) {
|
|
5821
6206
|
if (baseText === theirText) continue;
|
|
5822
6207
|
writes.push({ path: outPath, content: theirText });
|
|
5823
6208
|
shards.push({ path: rel, added: true, changed: true });
|
|
5824
6209
|
continue;
|
|
5825
6210
|
}
|
|
5826
|
-
const oursText =
|
|
6211
|
+
const oursText = readFileSync13(outPath, "utf8");
|
|
5827
6212
|
if (isManifestEntry(rel)) {
|
|
5828
6213
|
if (theirText !== oursText) manifestsKept.push(rel);
|
|
5829
6214
|
shards.push({ path: rel, added: false, changed: false });
|
|
@@ -5864,23 +6249,23 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5864
6249
|
shards.push({ path: rel, result, added: false, changed });
|
|
5865
6250
|
}
|
|
5866
6251
|
let gameScopes;
|
|
5867
|
-
const local2 = projectSides && discoverGameScopes(
|
|
6252
|
+
const local2 = projectSides && discoverGameScopes(resolve8(projectDir), projectSides.ours).gameScopes;
|
|
5868
6253
|
if (projectSides && local2) {
|
|
5869
6254
|
const world = planReturnedWorld(projectSides.ours, projectSides.base, projectSides.theirs, projectSides.merged, local2);
|
|
5870
6255
|
if (world.write) {
|
|
5871
6256
|
writes.push(world.write);
|
|
5872
6257
|
gameScopes = { path: world.write.path };
|
|
5873
|
-
} else if (world.error) gameScopes = { path:
|
|
6258
|
+
} else if (world.error) gameScopes = { path: join12(local2.dir, GAME_SCOPES_FILE), error: world.error };
|
|
5874
6259
|
}
|
|
5875
6260
|
const handoffs = [];
|
|
5876
6261
|
for (const [rel, theirText] of [...returnedDoc.handoffs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
5877
6262
|
const outPath = containedWrite(projectDir, rel);
|
|
5878
|
-
if (!
|
|
6263
|
+
if (!existsSync8(outPath)) {
|
|
5879
6264
|
writes.push({ path: outPath, content: theirText });
|
|
5880
6265
|
handoffs.push(rel);
|
|
5881
6266
|
continue;
|
|
5882
6267
|
}
|
|
5883
|
-
const merged = mergeHandoff(parseHandoff(
|
|
6268
|
+
const merged = mergeHandoff(parseHandoff(readFileSync13(outPath, "utf8")), parseHandoff(theirText));
|
|
5884
6269
|
if (merged) {
|
|
5885
6270
|
writes.push({ path: outPath, content: serialiseHandoff(merged) });
|
|
5886
6271
|
handoffs.push(rel);
|
|
@@ -5913,8 +6298,8 @@ function projectEntry(shards) {
|
|
|
5913
6298
|
return at.length === 1 ? at[0] : void 0;
|
|
5914
6299
|
}
|
|
5915
6300
|
function containedWrite(dir, name) {
|
|
5916
|
-
const root =
|
|
5917
|
-
const full =
|
|
6301
|
+
const root = resolve8(dir);
|
|
6302
|
+
const full = resolve8(join12(dir, name));
|
|
5918
6303
|
if (full !== root && !full.startsWith(root + sep5)) {
|
|
5919
6304
|
throw new UnsafeEntryError(`document entry escapes the target directory: ${name}`);
|
|
5920
6305
|
}
|
|
@@ -5922,7 +6307,7 @@ function containedWrite(dir, name) {
|
|
|
5922
6307
|
}
|
|
5923
6308
|
|
|
5924
6309
|
// src/resolutions.ts
|
|
5925
|
-
import { readFileSync as
|
|
6310
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
5926
6311
|
import { canonicalStringify as canonicalStringify12, parseSource as parseSource9 } from "@patterkit/core";
|
|
5927
6312
|
import { DEFAULT_QUALIFIERS as DEFAULT_QUALIFIERS2 } from "@patterkit/model";
|
|
5928
6313
|
import { clearUnresolved, unresolvedOf } from "@wildwinter/toolkit/extensions";
|
|
@@ -6070,7 +6455,7 @@ function findMarkedShards(root) {
|
|
|
6070
6455
|
for (const file of SHARD_EXTENSIONS.flatMap((ext) => tree.get(ext)).sort()) {
|
|
6071
6456
|
let text;
|
|
6072
6457
|
try {
|
|
6073
|
-
text =
|
|
6458
|
+
text = readFileSync14(file, "utf8");
|
|
6074
6459
|
} catch {
|
|
6075
6460
|
continue;
|
|
6076
6461
|
}
|
|
@@ -6087,7 +6472,7 @@ function findMarkedShards(root) {
|
|
|
6087
6472
|
if (sidecars.has(sidecarPath)) {
|
|
6088
6473
|
found.sidecarPath = sidecarPath;
|
|
6089
6474
|
try {
|
|
6090
|
-
const s = JSON.parse(
|
|
6475
|
+
const s = JSON.parse(readFileSync14(sidecarPath, "utf8"));
|
|
6091
6476
|
found.sidecar = { ...typeof s.type === "string" ? { type: s.type } : {}, conflicts: Array.isArray(s.conflicts) ? s.conflicts : [], warnings: Array.isArray(s.warnings) ? s.warnings : [] };
|
|
6092
6477
|
} catch {
|
|
6093
6478
|
}
|
|
@@ -6099,12 +6484,21 @@ function findMarkedShards(root) {
|
|
|
6099
6484
|
|
|
6100
6485
|
// src/cast-rename.ts
|
|
6101
6486
|
import { castStringKey as castStringKey2, speakerLabelKey as speakerLabelKey2, walkNodes as walkNodes6 } from "@patterkit/model";
|
|
6487
|
+
import { defaultPlaceholderVoice as defaultPlaceholderVoice2 } from "@patterkit/core";
|
|
6102
6488
|
function keepCastColours(cast, renames) {
|
|
6103
6489
|
for (const [from, to] of renames) {
|
|
6104
6490
|
const m = cast.find((c) => c.name === to);
|
|
6105
6491
|
if (m && !isColourSlot(m.colour)) m.colour = colourIndex(from);
|
|
6106
6492
|
}
|
|
6107
6493
|
}
|
|
6494
|
+
function keepPlaceholderVoices(cast, renames, accent) {
|
|
6495
|
+
for (const [from, to] of renames) {
|
|
6496
|
+
const m = cast.find((c) => c.name === to);
|
|
6497
|
+
if (!m || m.placeholderVoice) continue;
|
|
6498
|
+
const voice = defaultPlaceholderVoice2(from, accent, m.gender);
|
|
6499
|
+
if (voice) m.placeholderVoice = voice;
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6108
6502
|
function cleanCastRenames(renames) {
|
|
6109
6503
|
const out = /* @__PURE__ */ new Map();
|
|
6110
6504
|
for (const [from, to] of Object.entries(renames ?? {})) if (from && to && from !== to) out.set(from, to);
|
|
@@ -6162,7 +6556,7 @@ function renameCastStrings(loc, renames) {
|
|
|
6162
6556
|
}
|
|
6163
6557
|
|
|
6164
6558
|
// src/suggestions.ts
|
|
6165
|
-
import { readFileSync as
|
|
6559
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6166
6560
|
import { canonicalStringify as canonicalStringify13, parseSource as parseSource10 } from "@patterkit/core";
|
|
6167
6561
|
import { walkNodes as walkNodes7, AUTHORING_SCHEMA as AUTHORING_SCHEMA3 } from "@patterkit/model";
|
|
6168
6562
|
function staleParts(s, liveText, line) {
|
|
@@ -6249,7 +6643,7 @@ var Working = class {
|
|
|
6249
6643
|
if (!path) return void 0;
|
|
6250
6644
|
let file = this.flows.get(path);
|
|
6251
6645
|
if (!file) {
|
|
6252
|
-
const onDisk = parseSource10(
|
|
6646
|
+
const onDisk = parseSource10(readFileSync15(path, "utf8"));
|
|
6253
6647
|
const scene = this.loaded.scenes.find((s) => s.id === sceneId);
|
|
6254
6648
|
file = { ...onDisk, scene: structuredClone(scene ?? onDisk.scene) };
|
|
6255
6649
|
this.flows.set(path, file);
|
|
@@ -6729,11 +7123,11 @@ function readParagraphs(ps, joiner) {
|
|
|
6729
7123
|
c.walk(p, "both");
|
|
6730
7124
|
return c;
|
|
6731
7125
|
});
|
|
6732
|
-
const
|
|
7126
|
+
const join13 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
|
|
6733
7127
|
return {
|
|
6734
7128
|
text: {
|
|
6735
|
-
proposed:
|
|
6736
|
-
original:
|
|
7129
|
+
proposed: join13("proposed"),
|
|
7130
|
+
original: join13("original"),
|
|
6737
7131
|
changes: parts.flatMap((c) => c.changes),
|
|
6738
7132
|
comments: [...new Set(parts.flatMap((c) => c.comments))],
|
|
6739
7133
|
droppedFormatting: parts.reduce((n, c) => n + c.dropped, 0)
|
|
@@ -7315,6 +7709,7 @@ export {
|
|
|
7315
7709
|
PATTER_SCOPES_FILE,
|
|
7316
7710
|
PATTER_SCOPES_OWNER,
|
|
7317
7711
|
PATTER_VCS_FORMAT,
|
|
7712
|
+
PLACEHOLDER_SAMPLE_RATE,
|
|
7318
7713
|
PROJECT_HANDLED,
|
|
7319
7714
|
RARE_REACH_PCT,
|
|
7320
7715
|
REFUSE_MISSING_SHARE,
|
|
@@ -7322,6 +7717,7 @@ export {
|
|
|
7322
7717
|
SCRIPT_FONT_FILES,
|
|
7323
7718
|
SCRIPT_FONT_PACKAGES,
|
|
7324
7719
|
SHARD_EXTENSIONS,
|
|
7720
|
+
SpeechError,
|
|
7325
7721
|
UnsafeEntryError,
|
|
7326
7722
|
UnsupportedMergeError,
|
|
7327
7723
|
actionTextChanges,
|
|
@@ -7355,19 +7751,23 @@ export {
|
|
|
7355
7751
|
defaultGameScopesDir,
|
|
7356
7752
|
describeLocExport,
|
|
7357
7753
|
describeLocImport,
|
|
7754
|
+
describeSpeechError,
|
|
7358
7755
|
detectMergeType,
|
|
7359
7756
|
discoverGameScopes,
|
|
7360
7757
|
drivableScopes,
|
|
7361
7758
|
effectDescriber,
|
|
7759
|
+
expandKeyPath,
|
|
7362
7760
|
exportBlockers,
|
|
7363
7761
|
exportEditableScript,
|
|
7364
7762
|
extractLoc,
|
|
7365
7763
|
findMarkedShards,
|
|
7366
7764
|
findProjectFile,
|
|
7765
|
+
finishTake,
|
|
7367
7766
|
formatMarker,
|
|
7368
7767
|
gameScopeTokens,
|
|
7369
7768
|
gameScopesCatalogue,
|
|
7370
7769
|
gameScopesSnapshot,
|
|
7770
|
+
gcloudCredentialsPath,
|
|
7371
7771
|
handoffPath,
|
|
7372
7772
|
handoffWrite,
|
|
7373
7773
|
hostScopeTokens,
|
|
@@ -7377,6 +7777,7 @@ export {
|
|
|
7377
7777
|
issueMarkerCodes,
|
|
7378
7778
|
jsonToCatalog,
|
|
7379
7779
|
keepCastColours,
|
|
7780
|
+
keepPlaceholderVoices,
|
|
7380
7781
|
leastReachedFirst,
|
|
7381
7782
|
listGameDataFields,
|
|
7382
7783
|
listOpenSuggestions,
|
|
@@ -7385,8 +7786,10 @@ export {
|
|
|
7385
7786
|
loadProjectLanding,
|
|
7386
7787
|
loadedExtensions,
|
|
7387
7788
|
locLineHomes,
|
|
7789
|
+
manifestLineIds,
|
|
7388
7790
|
manifestPath,
|
|
7389
7791
|
markerLength,
|
|
7792
|
+
mintToken,
|
|
7390
7793
|
moveCastKeys,
|
|
7391
7794
|
newHandoffId,
|
|
7392
7795
|
parseHandoff,
|
|
@@ -7394,10 +7797,12 @@ export {
|
|
|
7394
7797
|
patterScopesStale,
|
|
7395
7798
|
patterScopesWrite,
|
|
7396
7799
|
pickAudio,
|
|
7800
|
+
placeholderAccentOf,
|
|
7397
7801
|
planBuild,
|
|
7398
7802
|
planEditableImport,
|
|
7399
7803
|
planExtensionUpgrades,
|
|
7400
7804
|
planPins,
|
|
7805
|
+
planPlaceholderSpeech,
|
|
7401
7806
|
planReturnedWorld,
|
|
7402
7807
|
planShareScopes,
|
|
7403
7808
|
planWorldSave,
|
|
@@ -7410,6 +7815,8 @@ export {
|
|
|
7410
7815
|
reachabilityIssues,
|
|
7411
7816
|
readEditableDocx,
|
|
7412
7817
|
readGameScopes,
|
|
7818
|
+
readGeneratedStampFromFile,
|
|
7819
|
+
readGoogleKey,
|
|
7413
7820
|
readHandoff,
|
|
7414
7821
|
readHandoffs,
|
|
7415
7822
|
readMarker,
|
|
@@ -7423,7 +7830,9 @@ export {
|
|
|
7423
7830
|
renderCoverageText,
|
|
7424
7831
|
renderPlay,
|
|
7425
7832
|
renderReportText,
|
|
7833
|
+
requestHash,
|
|
7426
7834
|
resolveDocumentation,
|
|
7835
|
+
resolveKeyFile,
|
|
7427
7836
|
resolveStart,
|
|
7428
7837
|
runCoverage,
|
|
7429
7838
|
runCoverageAsync,
|
|
@@ -7436,6 +7845,7 @@ export {
|
|
|
7436
7845
|
runInit,
|
|
7437
7846
|
runMerge,
|
|
7438
7847
|
runPack,
|
|
7848
|
+
runPlaceholderSpeech,
|
|
7439
7849
|
runPlay,
|
|
7440
7850
|
runPropertyUsage,
|
|
7441
7851
|
runReplace,
|
|
@@ -7464,14 +7874,19 @@ export {
|
|
|
7464
7874
|
setCut,
|
|
7465
7875
|
sidecarIssues,
|
|
7466
7876
|
soundManifest,
|
|
7877
|
+
sourceStrings,
|
|
7467
7878
|
stageDialect,
|
|
7468
7879
|
stageGateCount,
|
|
7469
7880
|
staleParts,
|
|
7881
|
+
storedKeyPath,
|
|
7882
|
+
synthesize,
|
|
7883
|
+
tokenSource,
|
|
7470
7884
|
unresolvedClashes,
|
|
7471
7885
|
unresolvedMarkIssues,
|
|
7472
7886
|
useScriptFontFiles,
|
|
7473
7887
|
valueAtClashPath,
|
|
7474
7888
|
vcsConfigWrites,
|
|
7889
|
+
voiceFor,
|
|
7475
7890
|
voiceScriptToXlsx,
|
|
7476
7891
|
worldSettingsScopes,
|
|
7477
7892
|
xlsxToCatalog
|