@mengine/medeo-client 3.0.0 → 3.0.1-dsl.1
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/README.md +4 -0
- package/dist/{dsl-Cpk8WxcZ.js → dsl-BSX7hMcR.js} +320 -81
- package/dist/dsl.d.ts +1 -1
- package/dist/dsl.js +1 -1
- package/dist/{index-Dldi944U.d.ts → index-D1ZC5V8t.d.ts} +7 -3
- package/dist/{index-DuM7I4Ku.d.ts → index-rW40K2LR.d.ts} +15 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -2
- package/dist/schemas.d.ts +606 -0
- package/dist/schemas.js +478 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +35 -14
- package/package.json +10 -8
|
@@ -145,8 +145,12 @@ function assertCaptionContent(value) {
|
|
|
145
145
|
}
|
|
146
146
|
const phonemeFields = {
|
|
147
147
|
...selectionFields,
|
|
148
|
-
phonemeScript: optional$1(
|
|
148
|
+
phonemeScript: optional$1(nonempty)
|
|
149
149
|
};
|
|
150
|
+
/** D190: omitted pronunciation uses the selected source words; an authored pronunciation is never empty. */
|
|
151
|
+
function assertPhonemeScript(value) {
|
|
152
|
+
if (value !== void 0 && !nonempty(value)) throw new Error("phonemeScript must be nonempty when provided");
|
|
153
|
+
}
|
|
150
154
|
function assertPhoneme(value) {
|
|
151
155
|
if (!segmentSelection(phonemeFields)(value)) throw new Error("Invalid phoneme");
|
|
152
156
|
}
|
|
@@ -638,6 +642,63 @@ function assertContainTree(rows, tree) {
|
|
|
638
642
|
}
|
|
639
643
|
}
|
|
640
644
|
//#endregion
|
|
645
|
+
//#region ../medeo-dsl/src/clip-playback.ts
|
|
646
|
+
const playable = {
|
|
647
|
+
video_clip: ["video", "image"],
|
|
648
|
+
speech: ["audio", "voiceover-recording"],
|
|
649
|
+
caption: ["caption"],
|
|
650
|
+
bgm: ["audio"]
|
|
651
|
+
};
|
|
652
|
+
function supportsClipSpeed(source) {
|
|
653
|
+
return isRecord(source) && [
|
|
654
|
+
"video",
|
|
655
|
+
"audio",
|
|
656
|
+
"voiceover-recording"
|
|
657
|
+
].includes(String(source.kind));
|
|
658
|
+
}
|
|
659
|
+
/** Content-kind admission is shared by Actions, script compilation and the final transaction. */
|
|
660
|
+
function assertClipSourceRole(role, kind) {
|
|
661
|
+
if (!playable[role]?.includes(kind)) throw new Error(`A ${role} Track does not play ${kind}`);
|
|
662
|
+
}
|
|
663
|
+
function assertClipWindow(window, duration) {
|
|
664
|
+
if (!Number.isSafeInteger(window.inMs) || !Number.isSafeInteger(window.outMs) || window.inMs < 0 || window.outMs <= window.inMs) throw new Error("Invalid Clip window");
|
|
665
|
+
if (duration !== void 0 && window.outMs > duration) throw new Error(`Invalid Clip window: ${window.outMs}ms is past its content's ${duration}ms`);
|
|
666
|
+
}
|
|
667
|
+
/** Check authored playback facts; a remote merge is still read by projection with diagnostics. */
|
|
668
|
+
function assertClipPlayback(clip, source, role) {
|
|
669
|
+
if (!isRecord(source)) throw new Error("Invalid Clip source");
|
|
670
|
+
const kind = source.kind ?? "caption";
|
|
671
|
+
const marker = clip.seqMarker;
|
|
672
|
+
assertSeqMarker(marker);
|
|
673
|
+
if (marker.outMs <= marker.inMs) throw new Error("Clip window must be nonempty");
|
|
674
|
+
if (role !== void 0) assertClipSourceRole(role, kind);
|
|
675
|
+
if (marker.rate !== void 0 && !supportsClipSpeed(source)) throw new Error("Only video and sound change speed");
|
|
676
|
+
const duration = typeof source.durationMs === "number" ? source.durationMs : void 0;
|
|
677
|
+
if (marker.fillTimeline) {
|
|
678
|
+
if (kind !== "audio") throw new Error("Only BGM audio fills the Timeline");
|
|
679
|
+
if (marker.inMs !== 0 || marker.rate !== void 0 && marker.rate !== 1 || marker.outMs !== duration) throw new Error(`A Clip that fills the Timeline plays its whole source at 1×: inMs 0, outMs ${duration ?? "its length"}, no rate; the Timeline's end cuts it`);
|
|
680
|
+
}
|
|
681
|
+
assertClipWindow(marker);
|
|
682
|
+
}
|
|
683
|
+
function clipParents(tree) {
|
|
684
|
+
const result = /* @__PURE__ */ new Map();
|
|
685
|
+
for (const track of tree) for (const clip of track.children ?? []) result.set(clip.entityId, track.entityId);
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
688
|
+
/** A new parent or Track role revalidates that binding, without blocking unrelated merged rows. */
|
|
689
|
+
function assertClipPlacements(before, after, previousTree, nextTree) {
|
|
690
|
+
const previous = clipParents(previousTree);
|
|
691
|
+
for (const [id, parent] of clipParents(nextTree)) {
|
|
692
|
+
const track = after["track-entities"][parent];
|
|
693
|
+
const previousTrack = before["track-entities"][parent];
|
|
694
|
+
if (previous.get(id) === parent && (track === void 0 || isRecord(track) && isRecord(previousTrack) && previousTrack.role === track.role)) continue;
|
|
695
|
+
const role = requireEntity(after, "track-entities", parent).role;
|
|
696
|
+
const clip = after["clip-entities"][id];
|
|
697
|
+
if (!clip) continue;
|
|
698
|
+
assertClipPlayback(clip, after["media-entities"][clip.source] ?? after["caption-entities"][clip.source], role);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
//#endregion
|
|
641
702
|
//#region ../medeo-dsl/src/plan.ts
|
|
642
703
|
/** Include removed nested keys: a partial merge alone would conceal a deleted required field. */
|
|
643
704
|
function replacementPatch(before, after) {
|
|
@@ -653,6 +714,7 @@ function replacementPatch(before, after) {
|
|
|
653
714
|
*/
|
|
654
715
|
function assertChangeAdmitted(change) {
|
|
655
716
|
const { before, after } = change;
|
|
717
|
+
const parents = change.containment ? clipParents(change.containment.after) : void 0;
|
|
656
718
|
for (const [table, rows] of change.entities) for (const [id, fields] of rows) {
|
|
657
719
|
const value = after[table][id];
|
|
658
720
|
if (value === void 0) continue;
|
|
@@ -667,25 +729,17 @@ function assertChangeAdmitted(change) {
|
|
|
667
729
|
}
|
|
668
730
|
const row = value;
|
|
669
731
|
for (const field of referenceFields[table] ?? []) if (!old || fields.has(field)) assertReferenceAdmitted(after, table, id, field, row[field]);
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
732
|
+
if (table === "clip-entities" && (!old || fields.has("seqMarker") || fields.has("source"))) {
|
|
733
|
+
const parent = parents?.get(id);
|
|
734
|
+
assertClipPlayback(row, after["media-entities"][row.source] ?? after["caption-entities"][row.source], parent === void 0 ? void 0 : requireEntity(after, "track-entities", parent).role);
|
|
735
|
+
}
|
|
673
736
|
}
|
|
674
737
|
if (change.entities.has("timeline-entities") && Object.keys(after["timeline-entities"]).length > 1) throw new Error("Document already has a Timeline");
|
|
675
738
|
for (const [table, ids] of change.relations) for (const id of ids) {
|
|
676
739
|
const value = after[table][id];
|
|
677
740
|
if (value !== void 0) assertRelationAdmitted(after, table, id, value);
|
|
678
741
|
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* A Clip that fills the Timeline plays its whole source at 1×: the player loops a short source and
|
|
682
|
-
* cuts a long one at the Timeline's end (RFC 02 §3.3), so a window or speed of its own has no meaning.
|
|
683
|
-
*/
|
|
684
|
-
function assertFillsWithWholeSource(rows, marker, source) {
|
|
685
|
-
if (marker?.fillTimeline !== true) return;
|
|
686
|
-
const media = typeof source === "string" ? rows["media-entities"][source] : void 0;
|
|
687
|
-
const durationMs = typeof media?.durationMs === "number" ? media.durationMs : void 0;
|
|
688
|
-
if (marker.inMs !== 0 || marker.rate !== void 0 && marker.rate !== 1 || durationMs !== void 0 && marker.outMs !== durationMs) throw new Error(`A Clip that fills the Timeline plays its whole source at 1×: inMs 0, outMs ${durationMs ?? "its length"}, no rate; the Timeline's end cuts it`);
|
|
742
|
+
if (change.containment) assertClipPlacements(before, after, change.containment.before, change.containment.after);
|
|
689
743
|
}
|
|
690
744
|
//#endregion
|
|
691
745
|
//#region src/dsl/projection/types.ts
|
|
@@ -921,15 +975,11 @@ function requireSource(s, id, role) {
|
|
|
921
975
|
return media;
|
|
922
976
|
}
|
|
923
977
|
function assertMediaRole(media, role) {
|
|
924
|
-
if (!media
|
|
925
|
-
|
|
926
|
-
speech: ["audio", "voiceover-recording"],
|
|
927
|
-
bgm: ["audio"],
|
|
928
|
-
caption: []
|
|
929
|
-
}[role].includes(media.kind)) throw new Error(`Source is not allowed on ${role} Track`);
|
|
978
|
+
if (!media) throw new Error(`Missing source for ${role} Track`);
|
|
979
|
+
assertClipSourceRole(role, media.kind);
|
|
930
980
|
}
|
|
931
981
|
function validateWindow(window, duration) {
|
|
932
|
-
|
|
982
|
+
assertClipWindow(window, duration);
|
|
933
983
|
}
|
|
934
984
|
function trackOf(s, id) {
|
|
935
985
|
requireClip(s, id);
|
|
@@ -1217,14 +1267,15 @@ function replaceSourcePlan(s, clipId, input) {
|
|
|
1217
1267
|
patch: {
|
|
1218
1268
|
seqMarker: {
|
|
1219
1269
|
inMs: input.inMs,
|
|
1220
|
-
outMs: input.outMs
|
|
1270
|
+
outMs: input.outMs,
|
|
1271
|
+
...supportsClipSpeed(source.source) ? {} : { rate: void 0 }
|
|
1221
1272
|
},
|
|
1222
1273
|
source: source.id
|
|
1223
1274
|
}
|
|
1224
1275
|
}];
|
|
1225
1276
|
}
|
|
1226
1277
|
//#endregion
|
|
1227
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1278
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/resolver.js
|
|
1228
1279
|
function getMapFieldSchema(schema, key) {
|
|
1229
1280
|
if (!schema) return void 0;
|
|
1230
1281
|
if (Object.prototype.hasOwnProperty.call(schema.definition, key)) return schema.definition[key];
|
|
@@ -1254,10 +1305,10 @@ function getChildContainerSchema(schema, childKey) {
|
|
|
1254
1305
|
}
|
|
1255
1306
|
}
|
|
1256
1307
|
//#endregion
|
|
1257
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1308
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/constants.js
|
|
1258
1309
|
const CID_KEY = "$cid";
|
|
1259
1310
|
//#endregion
|
|
1260
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1311
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/container-inference.js
|
|
1261
1312
|
function isPlainObjectValue(value) {
|
|
1262
1313
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp) && typeof value !== "function";
|
|
1263
1314
|
}
|
|
@@ -1296,7 +1347,7 @@ function getContainerValueKind(value) {
|
|
|
1296
1347
|
return "other";
|
|
1297
1348
|
}
|
|
1298
1349
|
//#endregion
|
|
1299
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1350
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/utils.js
|
|
1300
1351
|
/**
|
|
1301
1352
|
* Utility functions for Loro Mirror core
|
|
1302
1353
|
*/
|
|
@@ -1720,7 +1771,7 @@ function safeStringify(value, indent = 2) {
|
|
|
1720
1771
|
return JSON.stringify(replacer(value), null, indent);
|
|
1721
1772
|
}
|
|
1722
1773
|
//#endregion
|
|
1723
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1774
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/validators.js
|
|
1724
1775
|
const schemaValidationCache = /* @__PURE__ */ new WeakMap();
|
|
1725
1776
|
function isCacheableValue(value) {
|
|
1726
1777
|
return typeof value === "object" && value !== null;
|
|
@@ -2000,7 +2051,7 @@ function getDefaultValue(schema) {
|
|
|
2000
2051
|
}
|
|
2001
2052
|
}
|
|
2002
2053
|
//#endregion
|
|
2003
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2054
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/index.js
|
|
2004
2055
|
/**
|
|
2005
2056
|
* Create a schema definition
|
|
2006
2057
|
*/
|
|
@@ -2765,7 +2816,7 @@ const produce = new Immer2({
|
|
|
2765
2816
|
useStrictShallowCopy: true
|
|
2766
2817
|
}).produce;
|
|
2767
2818
|
//#endregion
|
|
2768
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2819
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/debounce-timer.js
|
|
2769
2820
|
/**
|
|
2770
2821
|
* Deadline-based debounce timer.
|
|
2771
2822
|
*
|
|
@@ -2823,7 +2874,7 @@ var DebounceTimer = class {
|
|
|
2823
2874
|
}
|
|
2824
2875
|
};
|
|
2825
2876
|
//#endregion
|
|
2826
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2877
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/ephemeral.js
|
|
2827
2878
|
/**
|
|
2828
2879
|
* Manages ephemeral patches: storage, composition, path resolution, and finalization.
|
|
2829
2880
|
*/
|
|
@@ -3098,7 +3149,7 @@ var EphemeralPatchManager = class {
|
|
|
3098
3149
|
}
|
|
3099
3150
|
};
|
|
3100
3151
|
//#endregion
|
|
3101
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3152
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/tree-utils.js
|
|
3102
3153
|
function normalizeTreeJson(input, options) {
|
|
3103
3154
|
if (!Array.isArray(input)) return [];
|
|
3104
3155
|
return input.map((node) => normalizeTreeNode(node, options));
|
|
@@ -3121,7 +3172,7 @@ function isPlainRecord(value) {
|
|
|
3121
3172
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3122
3173
|
}
|
|
3123
3174
|
//#endregion
|
|
3124
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3175
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/loroEventApply.js
|
|
3125
3176
|
function isJSONObject(v) {
|
|
3126
3177
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3127
3178
|
}
|
|
@@ -3543,7 +3594,7 @@ function isIgnoredByAncestor(id, ignoreSet, getContainerById) {
|
|
|
3543
3594
|
return false;
|
|
3544
3595
|
}
|
|
3545
3596
|
//#endregion
|
|
3546
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3597
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/diff.js
|
|
3547
3598
|
/**
|
|
3548
3599
|
* Finds the longest increasing subsequence of a sequence of numbers
|
|
3549
3600
|
* @param sequence The sequence of numbers
|
|
@@ -4274,7 +4325,7 @@ function diffMap(doc, oldState, newState, containerId, schema, inferOptions) {
|
|
|
4274
4325
|
return changes;
|
|
4275
4326
|
}
|
|
4276
4327
|
//#endregion
|
|
4277
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
4328
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/mirror.js
|
|
4278
4329
|
/**
|
|
4279
4330
|
* Mirror core functionality for bidirectional sync between app state and Loro CRDT
|
|
4280
4331
|
*/
|
|
@@ -5845,6 +5896,30 @@ const phonemeCodec = codec((v) => {
|
|
|
5845
5896
|
assertPhoneme(value);
|
|
5846
5897
|
return value;
|
|
5847
5898
|
});
|
|
5899
|
+
/** A row-owned binding couples the selection, pronunciation and its immutable file in one LWW value. */
|
|
5900
|
+
const phoneticBindingCodec = codec((value) => {
|
|
5901
|
+
assertEntity("phonetic-script-entities", value);
|
|
5902
|
+
assertPhoneticScript(void 0, { ...value });
|
|
5903
|
+
return JSON.stringify({
|
|
5904
|
+
phoneme: JSON.parse(phonemeCodec.encode(value.phoneme)),
|
|
5905
|
+
system: value.system,
|
|
5906
|
+
key: value.key,
|
|
5907
|
+
storageKey: value.storageKey,
|
|
5908
|
+
digest: value.digest
|
|
5909
|
+
});
|
|
5910
|
+
}, (raw) => {
|
|
5911
|
+
const value = JSON.parse(raw);
|
|
5912
|
+
if (!isRecord(value)) throw new Error("Expected a PhoneticScript binding");
|
|
5913
|
+
const phoneme = phonemeCodec.decode(JSON.stringify(value.phoneme));
|
|
5914
|
+
if (isInvalidAtomic(phoneme)) throw new Error(phoneme.reason);
|
|
5915
|
+
const binding = {
|
|
5916
|
+
...value,
|
|
5917
|
+
phoneme
|
|
5918
|
+
};
|
|
5919
|
+
assertEntity("phonetic-script-entities", binding);
|
|
5920
|
+
assertPhoneticScript(void 0, binding);
|
|
5921
|
+
return binding;
|
|
5922
|
+
});
|
|
5848
5923
|
/** A Clip's sequence marker is stored as one string, so it is taken or replaced whole. */
|
|
5849
5924
|
const seqMarkerCodec = codec((v) => {
|
|
5850
5925
|
assertSeqMarker(v);
|
|
@@ -5911,13 +5986,7 @@ const medeoDslLoroShape = schema({
|
|
|
5911
5986
|
storageKey: schema.String(optional),
|
|
5912
5987
|
digest: schema.String(optional)
|
|
5913
5988
|
})),
|
|
5914
|
-
"phonetic-script-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
5915
|
-
phoneme: atomicValue(phonemeCodec),
|
|
5916
|
-
system: schema.String(optional),
|
|
5917
|
-
key: schema.String(optional),
|
|
5918
|
-
storageKey: schema.String(optional),
|
|
5919
|
-
digest: schema.String(optional)
|
|
5920
|
-
})),
|
|
5989
|
+
"phonetic-script-entities": schema.LoroMapRecord(schema.LoroMap({ binding: atomicValue(phoneticBindingCodec) })),
|
|
5921
5990
|
"caption-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
5922
5991
|
selection: optionalAtomicValue(captionContentCodec),
|
|
5923
5992
|
text: schema.LoroText(optional),
|
|
@@ -5969,20 +6038,78 @@ function documentFormat(doc) {
|
|
|
5969
6038
|
}
|
|
5970
6039
|
var UnsupportedDocumentFormatError = class extends Error {
|
|
5971
6040
|
code = "unsupported_document_format";
|
|
5972
|
-
constructor() {
|
|
5973
|
-
super(
|
|
6041
|
+
constructor(message = "Expected a new DSL document; legacy, mixed or uninitialized content is not supported") {
|
|
6042
|
+
super(message);
|
|
5974
6043
|
this.name = "UnsupportedDocumentFormatError";
|
|
5975
6044
|
}
|
|
5976
6045
|
};
|
|
5977
6046
|
/** Run after initial import, before opening an editable handle. Empty new documents still have one Timeline. */
|
|
5978
6047
|
function assertDslDocument(doc) {
|
|
5979
6048
|
if (documentFormat(doc) !== "dsl" || doc.getMap("timeline-entities").keys().length !== 1) throw new UnsupportedDocumentFormatError();
|
|
6049
|
+
assertPhoneticBindingFormat(doc);
|
|
6050
|
+
}
|
|
6051
|
+
/** This storage cutover has no field-per-fact reader or mixed-version write path. */
|
|
6052
|
+
function assertPhoneticBindingFormat(doc) {
|
|
6053
|
+
if (!Object.hasOwn(doc.getShallowValue(), "phonetic-script-entities")) return;
|
|
6054
|
+
const table = doc.getMap("phonetic-script-entities");
|
|
6055
|
+
for (const id of table.keys()) {
|
|
6056
|
+
const row = table.get(id);
|
|
6057
|
+
if (!isContainer(row) || row.kind() !== "Map" || row.keys().some((key) => key !== "binding") || typeof row.get("binding") !== "string") throw new UnsupportedDocumentFormatError("PhoneticScript storage requires one atomic binding; old DSL documents need explicit migration");
|
|
6058
|
+
}
|
|
5980
6059
|
}
|
|
5981
6060
|
//#endregion
|
|
5982
6061
|
//#region ../medeo-dsl/src/schema/text-styles.ts
|
|
5983
6062
|
/** How rich-text marks extend at their edges: a language runs on into text typed after it. */
|
|
5984
6063
|
const textStyles = { language: { expand: "after" } };
|
|
5985
6064
|
//#endregion
|
|
6065
|
+
//#region src/dsl/document/phonetic-binding.ts
|
|
6066
|
+
/**
|
|
6067
|
+
* Actions and the transaction guard keep their domain-shaped rows. The native schema stores
|
|
6068
|
+
* a PhoneticScript's facts under one binding; this lens applies draft edits to that single value.
|
|
6069
|
+
* It never reads the retired field-per-fact representation.
|
|
6070
|
+
*/
|
|
6071
|
+
function logicalDslState(native) {
|
|
6072
|
+
const phonetics = new Proxy({}, {
|
|
6073
|
+
get: (_target, key) => typeof key === "string" ? native["phonetic-script-entities"]?.[key]?.binding : void 0,
|
|
6074
|
+
set: (_target, key, value) => {
|
|
6075
|
+
if (typeof key !== "string") throw new Error("Invalid PhoneticScript key");
|
|
6076
|
+
native["phonetic-script-entities"] ??= {};
|
|
6077
|
+
const existing = native["phonetic-script-entities"][key];
|
|
6078
|
+
if (existing) existing.binding = value;
|
|
6079
|
+
else native["phonetic-script-entities"][key] = { binding: value };
|
|
6080
|
+
return true;
|
|
6081
|
+
},
|
|
6082
|
+
deleteProperty: (_target, key) => {
|
|
6083
|
+
if (typeof key === "string") delete native["phonetic-script-entities"][key];
|
|
6084
|
+
return true;
|
|
6085
|
+
},
|
|
6086
|
+
ownKeys: () => Object.keys(native["phonetic-script-entities"] ?? {}),
|
|
6087
|
+
has: (_target, key) => typeof key === "string" && Object.hasOwn(native["phonetic-script-entities"] ?? {}, key),
|
|
6088
|
+
getOwnPropertyDescriptor: (_target, key) => typeof key === "string" && Object.hasOwn(native["phonetic-script-entities"] ?? {}, key) ? {
|
|
6089
|
+
configurable: true,
|
|
6090
|
+
enumerable: true,
|
|
6091
|
+
writable: true,
|
|
6092
|
+
value: native["phonetic-script-entities"][key].binding
|
|
6093
|
+
} : void 0
|
|
6094
|
+
});
|
|
6095
|
+
const view = {};
|
|
6096
|
+
for (const key of new Set([
|
|
6097
|
+
...Object.keys(native),
|
|
6098
|
+
...entityTables,
|
|
6099
|
+
...relationTables,
|
|
6100
|
+
"contain-relations"
|
|
6101
|
+
])) Object.defineProperty(view, key, {
|
|
6102
|
+
configurable: true,
|
|
6103
|
+
enumerable: true,
|
|
6104
|
+
get: () => key === "phonetic-script-entities" ? phonetics : Reflect.get(native, key),
|
|
6105
|
+
set: (value) => {
|
|
6106
|
+
if (key === "phonetic-script-entities") throw new Error("Cannot replace the PhoneticScript table");
|
|
6107
|
+
Reflect.set(native, key, value);
|
|
6108
|
+
}
|
|
6109
|
+
});
|
|
6110
|
+
return view;
|
|
6111
|
+
}
|
|
6112
|
+
//#endregion
|
|
5986
6113
|
//#region src/dsl/document/read.ts
|
|
5987
6114
|
/** Looking up a missing root must not create it. */
|
|
5988
6115
|
function rootMap(doc, name) {
|
|
@@ -6066,6 +6193,7 @@ function referenceTargets(table, row) {
|
|
|
6066
6193
|
}
|
|
6067
6194
|
}
|
|
6068
6195
|
function readMedeoDsl(doc, state) {
|
|
6196
|
+
const logical = logicalDslState(state);
|
|
6069
6197
|
const diagnostics = [];
|
|
6070
6198
|
const diagnostic = (code, path, error) => diagnostics.push({
|
|
6071
6199
|
code,
|
|
@@ -6075,7 +6203,7 @@ function readMedeoDsl(doc, state) {
|
|
|
6075
6203
|
const entities = Object.fromEntries(entityTables.map((t) => [t, {}]));
|
|
6076
6204
|
const relations = Object.fromEntries(relationTables.map((t) => [t, {}]));
|
|
6077
6205
|
const richText = {};
|
|
6078
|
-
for (const table of entityTables) for (const [id, raw] of Object.entries(
|
|
6206
|
+
for (const table of entityTables) for (const [id, raw] of Object.entries(logical[table] ?? {})) try {
|
|
6079
6207
|
assertId(id);
|
|
6080
6208
|
let v = raw;
|
|
6081
6209
|
for (const field of optionalReferences[table] ?? []) {
|
|
@@ -6581,7 +6709,11 @@ function validateDraft(tracked) {
|
|
|
6581
6709
|
before,
|
|
6582
6710
|
after: state,
|
|
6583
6711
|
entities: tracked.entities,
|
|
6584
|
-
relations: tracked.relations
|
|
6712
|
+
relations: tracked.relations,
|
|
6713
|
+
containment: {
|
|
6714
|
+
before: plainTree(before["contain-relations"] ?? []),
|
|
6715
|
+
after: plainTree(state["contain-relations"] ?? [])
|
|
6716
|
+
}
|
|
6585
6717
|
});
|
|
6586
6718
|
if (!tracked.treeChanged) return;
|
|
6587
6719
|
const oldNodes = nodeIndex(before["contain-relations"] ?? []);
|
|
@@ -6658,8 +6790,11 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6658
6790
|
const steps = [];
|
|
6659
6791
|
const newCaptionTexts = /* @__PURE__ */ new Map();
|
|
6660
6792
|
const bodies = /* @__PURE__ */ new Map();
|
|
6661
|
-
const
|
|
6662
|
-
|
|
6793
|
+
const nativeBefore = mirror.getState();
|
|
6794
|
+
const before = logicalDslState(nativeBefore);
|
|
6795
|
+
const phoneticRows = /* @__PURE__ */ new Map();
|
|
6796
|
+
mirror.setState((native) => {
|
|
6797
|
+
const raw = logicalDslState(native);
|
|
6663
6798
|
const tracked = new TransactionDraft(raw, before);
|
|
6664
6799
|
setReject((error) => tracked.fail(error));
|
|
6665
6800
|
/** The current text of a Caption's own Text or a script segment, as this transaction leaves it so far. */
|
|
@@ -6851,6 +6986,15 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6851
6986
|
for (const step of steps) if (step.type === "text") if ("captionId" in step.target) requireEntity(raw, "caption-entities", step.target.captionId);
|
|
6852
6987
|
else requireSegment(raw, step.target.audioScriptId, step.target.segmentId);
|
|
6853
6988
|
assertSelectionsAfter(raw, before, tracked, steps);
|
|
6989
|
+
const changedPhonetics = new Set([...tracked.entities.get("phonetic-script-entities")?.keys() ?? [], ...steps.flatMap((step) => step.type !== "text" && step.table === "phonetic-script-entities" ? [step.id] : [])]);
|
|
6990
|
+
for (const id of changedPhonetics) {
|
|
6991
|
+
const row = raw["phonetic-script-entities"][id];
|
|
6992
|
+
phoneticRows.set(id, row ? copyInput(row) : void 0);
|
|
6993
|
+
if (!row) continue;
|
|
6994
|
+
const old = nativeBefore["phonetic-script-entities"][id];
|
|
6995
|
+
if (old) native["phonetic-script-entities"][id] = old;
|
|
6996
|
+
else delete native["phonetic-script-entities"][id];
|
|
6997
|
+
}
|
|
6854
6998
|
for (const [id, fields] of tracked.entities.get("caption-entities") ?? []) {
|
|
6855
6999
|
const row = raw["caption-entities"][id];
|
|
6856
7000
|
if (fields.has("text") && row?.text !== void 0 && before["caption-entities"][id]?.text === void 0) {
|
|
@@ -6859,6 +7003,7 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6859
7003
|
}
|
|
6860
7004
|
}
|
|
6861
7005
|
for (const table of Object.keys(selectionField)) {
|
|
7006
|
+
if (table === "phonetic-script-entities") continue;
|
|
6862
7007
|
const field = selectionField[table];
|
|
6863
7008
|
for (const [id, fields] of tracked.entities.get(table) ?? []) {
|
|
6864
7009
|
const row = raw[table][id];
|
|
@@ -6898,6 +7043,8 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6898
7043
|
applyTextEdits(text, step.edits);
|
|
6899
7044
|
continue;
|
|
6900
7045
|
}
|
|
7046
|
+
const phonetic = step.table === "phonetic-script-entities" ? phoneticRows.get(step.id) : void 0;
|
|
7047
|
+
if (step.table === "phonetic-script-entities" && !phonetic) continue;
|
|
6901
7048
|
const row = entityRow(doc, step.table, step.id);
|
|
6902
7049
|
const field = selectionField[step.table];
|
|
6903
7050
|
if (step.type === "range") {
|
|
@@ -6916,12 +7063,21 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6916
7063
|
if (!value.start || !value.end) throw new Error("Unable to construct prepared range");
|
|
6917
7064
|
}
|
|
6918
7065
|
if (step.phonemeScript !== void 0) value.phonemeScript = step.phonemeScript;
|
|
6919
|
-
if (
|
|
6920
|
-
|
|
6921
|
-
|
|
7066
|
+
if (phonetic) phonetic.phoneme = value;
|
|
7067
|
+
else {
|
|
7068
|
+
if (!row) throw new Error("Unable to construct prepared range");
|
|
7069
|
+
row.set(field, codecs[step.table].encode(value));
|
|
7070
|
+
}
|
|
7071
|
+
} else if (step.value !== void 0) if (phonetic) phonetic.phoneme = step.value;
|
|
7072
|
+
else {
|
|
6922
7073
|
if (!row) throw new Error("Prepared selection owner is missing");
|
|
6923
7074
|
row.set(field, codecs[step.table].encode(step.value));
|
|
6924
|
-
}
|
|
7075
|
+
}
|
|
7076
|
+
else if (row?.get(field) !== void 0) row.delete(field);
|
|
7077
|
+
}
|
|
7078
|
+
for (const [id, binding] of phoneticRows) {
|
|
7079
|
+
if (!binding) continue;
|
|
7080
|
+
(entityRow(doc, "phonetic-script-entities", id) ?? doc.getMap("phonetic-script-entities").setContainer(id, new LoroMap())).set("binding", phoneticBindingCodec.encode(binding));
|
|
6925
7081
|
}
|
|
6926
7082
|
}
|
|
6927
7083
|
});
|
|
@@ -6968,7 +7124,7 @@ function assertSelectionsAfter(raw, before, tracked, steps) {
|
|
|
6968
7124
|
if (!row) continue;
|
|
6969
7125
|
const phoneme = phonemes.has(id) ? phonemes.get(id) : row.phoneme;
|
|
6970
7126
|
const old = before["phonetic-script-entities"][id];
|
|
6971
|
-
assertPhoneticScript(old, {
|
|
7127
|
+
assertPhoneticScript(old ? { ...old } : void 0, {
|
|
6972
7128
|
...row,
|
|
6973
7129
|
phoneme: isRecord(phoneme) ? phoneme : void 0
|
|
6974
7130
|
});
|
|
@@ -7007,6 +7163,7 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7007
7163
|
"tracks",
|
|
7008
7164
|
"part_library"
|
|
7009
7165
|
].some((key) => Object.hasOwn(roots, key))) throw new Error("Expected a new DSL document, not a legacy content model");
|
|
7166
|
+
assertPhoneticBindingFormat(doc);
|
|
7010
7167
|
doc.configTextStyle(textStyles);
|
|
7011
7168
|
this.mirror = new Mirror({
|
|
7012
7169
|
doc,
|
|
@@ -7070,6 +7227,11 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7070
7227
|
editCaptionText(captionId, expectedBodyId, edits, audit) {
|
|
7071
7228
|
this.transact((tx) => tx.editCaptionText(captionId, edits, expectedBodyId), audit);
|
|
7072
7229
|
}
|
|
7230
|
+
/** Check the session's write capability before an Action starts host-owned resource I/O. */
|
|
7231
|
+
assertWritable() {
|
|
7232
|
+
this.assertReadyToWrite();
|
|
7233
|
+
this.options.mutationGuard?.run(() => {});
|
|
7234
|
+
}
|
|
7073
7235
|
/**
|
|
7074
7236
|
* A run inside one segment as native Cursors. Local ranges exclude insertions at their start and
|
|
7075
7237
|
* include insertions at their end. K01 remains deferred.
|
|
@@ -7102,13 +7264,7 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7102
7264
|
if (this.ownsDoc) this.doc.free();
|
|
7103
7265
|
}
|
|
7104
7266
|
mutate(run) {
|
|
7105
|
-
this.
|
|
7106
|
-
if (this.writing || this.notifying) {
|
|
7107
|
-
const error = /* @__PURE__ */ new Error("A DSL mutation is already running");
|
|
7108
|
-
this.rejectTransaction?.(error);
|
|
7109
|
-
throw error;
|
|
7110
|
-
}
|
|
7111
|
-
if (this.doc.getPendingTxnLength() !== 0) throw new Error("Commit external native edits before a DSL mutation");
|
|
7267
|
+
this.assertReadyToWrite();
|
|
7112
7268
|
const apply = () => {
|
|
7113
7269
|
this.writing = true;
|
|
7114
7270
|
try {
|
|
@@ -7123,6 +7279,15 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7123
7279
|
if (this.options.mutationGuard) this.options.mutationGuard.run(apply);
|
|
7124
7280
|
else apply();
|
|
7125
7281
|
}
|
|
7282
|
+
assertReadyToWrite() {
|
|
7283
|
+
this.assertActive();
|
|
7284
|
+
if (this.writing || this.notifying) {
|
|
7285
|
+
const error = /* @__PURE__ */ new Error("A DSL mutation is already running");
|
|
7286
|
+
this.rejectTransaction?.(error);
|
|
7287
|
+
throw error;
|
|
7288
|
+
}
|
|
7289
|
+
if (this.doc.getPendingTxnLength() !== 0) throw new Error("Commit external native edits before a DSL mutation");
|
|
7290
|
+
}
|
|
7126
7291
|
notify() {
|
|
7127
7292
|
if (this.disposed || !this.dirty) return;
|
|
7128
7293
|
this.dirty = false;
|
|
@@ -7149,12 +7314,13 @@ function spokenScripts(snapshot, recordingId) {
|
|
|
7149
7314
|
const found = /* @__PURE__ */ new Map();
|
|
7150
7315
|
const seen = /* @__PURE__ */ new Set();
|
|
7151
7316
|
const visit = (id, phoneticScriptId) => {
|
|
7152
|
-
if (seen.has(id)) return;
|
|
7153
|
-
seen.add(id);
|
|
7154
7317
|
if (snapshot["audio-script-entities"][id]) {
|
|
7155
|
-
if (!found.has(id)) found.set(id,
|
|
7318
|
+
if (!found.has(id)) found.set(id, /* @__PURE__ */ new Set());
|
|
7319
|
+
if (phoneticScriptId !== void 0) found.get(id).add(phoneticScriptId);
|
|
7156
7320
|
return;
|
|
7157
7321
|
}
|
|
7322
|
+
if (seen.has(id)) return;
|
|
7323
|
+
seen.add(id);
|
|
7158
7324
|
const phonetic = snapshot["phonetic-script-entities"][id];
|
|
7159
7325
|
if (phonetic) {
|
|
7160
7326
|
visit(phonetic.phoneme.audioScriptId, id);
|
|
@@ -7164,11 +7330,18 @@ function spokenScripts(snapshot, recordingId) {
|
|
|
7164
7330
|
};
|
|
7165
7331
|
visit(recordingId);
|
|
7166
7332
|
if (!found.size) {
|
|
7167
|
-
for (const r of relations) if (r.sourceId === recordingId && snapshot["audio-script-entities"][r.entityId]) found.set(r.entityId,
|
|
7333
|
+
for (const r of relations) if (r.sourceId === recordingId && snapshot["audio-script-entities"][r.entityId]) found.set(r.entityId, /* @__PURE__ */ new Set());
|
|
7168
7334
|
}
|
|
7169
|
-
return [...found].map(([audioScriptId,
|
|
7170
|
-
|
|
7171
|
-
|
|
7335
|
+
return [...found].map(([audioScriptId, ids]) => {
|
|
7336
|
+
const phoneticScriptIds = [...ids];
|
|
7337
|
+
if (phoneticScriptIds.length === 1) return {
|
|
7338
|
+
audioScriptId,
|
|
7339
|
+
phoneticScriptId: phoneticScriptIds[0]
|
|
7340
|
+
};
|
|
7341
|
+
return phoneticScriptIds.length ? {
|
|
7342
|
+
audioScriptId,
|
|
7343
|
+
phoneticScriptIds
|
|
7344
|
+
} : { audioScriptId };
|
|
7172
7345
|
});
|
|
7173
7346
|
}
|
|
7174
7347
|
/** Every AudioScript in the document with its PhoneticScripts, the Speech Clips saying it and the Caption Clips showing it. */
|
|
@@ -7213,7 +7386,6 @@ function toAudioScriptViews(snapshot) {
|
|
|
7213
7386
|
});
|
|
7214
7387
|
}
|
|
7215
7388
|
const layout = resolveDslLayout(snapshot);
|
|
7216
|
-
for (const view of views.values()) placeTranscript(snapshot, layout, view);
|
|
7217
7389
|
const timbres = Object.values(snapshot["voice-timbre-relations"]);
|
|
7218
7390
|
for (const track of layout.tracks) for (const { clipId } of track.clips) {
|
|
7219
7391
|
const source = snapshot["clip-entities"][clipId]?.source;
|
|
@@ -7237,6 +7409,7 @@ function toAudioScriptViews(snapshot) {
|
|
|
7237
7409
|
clipId,
|
|
7238
7410
|
recordingId: source,
|
|
7239
7411
|
...spoken.phoneticScriptId === void 0 ? {} : { phoneticScriptId: spoken.phoneticScriptId },
|
|
7412
|
+
...spoken.phoneticScriptIds === void 0 ? {} : { phoneticScriptIds: spoken.phoneticScriptIds },
|
|
7240
7413
|
voices,
|
|
7241
7414
|
...applied === void 0 ? {} : { applied }
|
|
7242
7415
|
});
|
|
@@ -7253,19 +7426,23 @@ function toAudioScriptViews(snapshot) {
|
|
|
7253
7426
|
segment
|
|
7254
7427
|
});
|
|
7255
7428
|
}
|
|
7429
|
+
for (const view of views.values()) placeTranscript(snapshot, layout, view);
|
|
7256
7430
|
return [...views.values()];
|
|
7257
7431
|
}
|
|
7258
7432
|
/**
|
|
7259
7433
|
* A transcript's segments on the Timeline: a segment's time is in the medium the script was read
|
|
7260
|
-
* off
|
|
7434
|
+
* off. An authored Caption anchor identifies its use of that medium; otherwise the earliest
|
|
7435
|
+
* Clip holding that source moment is used. The source time is never taken from the Caption.
|
|
7261
7436
|
*/
|
|
7262
7437
|
function placeTranscript(snapshot, layout, view) {
|
|
7263
7438
|
if (!view.sourceMediaIds.length) return;
|
|
7264
7439
|
const plays = layout.tracks.flatMap((track) => track.clips).filter((clip) => view.sourceMediaIds.includes(snapshot["clip-entities"][clip.clipId]?.source ?? "")).sort((a, b) => a.startMs - b.startMs);
|
|
7265
|
-
for (const segment of view.segments) {
|
|
7440
|
+
for (const [index, segment] of view.segments.entries()) {
|
|
7266
7441
|
const at = segment.startMs;
|
|
7267
7442
|
if (at === void 0) continue;
|
|
7268
|
-
const
|
|
7443
|
+
const anchoredUses = new Set(view.captions.filter((caption) => caption.segment === index).map((caption) => layout.clips[caption.clipId]?.anchorClipId));
|
|
7444
|
+
const candidates = plays.filter((c) => c.inMs <= at && at < c.outMs);
|
|
7445
|
+
const clip = candidates.find((c) => anchoredUses.has(c.clipId)) ?? candidates[0];
|
|
7269
7446
|
if (clip) segment.timelineMs = Math.round(clip.startMs + (at - clip.inMs) / clip.rate);
|
|
7270
7447
|
}
|
|
7271
7448
|
}
|
|
@@ -7276,14 +7453,26 @@ function placeTranscript(snapshot, layout, view) {
|
|
|
7276
7453
|
* not what a take was synthesized from (D190).
|
|
7277
7454
|
*/
|
|
7278
7455
|
function appliedSpeech(snapshot, recordingId, spoken) {
|
|
7279
|
-
const
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7456
|
+
const ids = spoken.phoneticScriptIds ?? (spoken.phoneticScriptId === void 0 ? [] : [spoken.phoneticScriptId]);
|
|
7457
|
+
if (!ids.length) return void 0;
|
|
7458
|
+
let unknown = false;
|
|
7459
|
+
for (const sourceId of ids) {
|
|
7460
|
+
const recorded = Object.values(snapshot["derived-from-relations"]).find((r) => r.entityId === recordingId && r.sourceId === sourceId)?.sourceDigest;
|
|
7461
|
+
const current = snapshot["phonetic-script-entities"][sourceId]?.digest;
|
|
7462
|
+
if (recorded === void 0 || current === void 0) unknown = true;
|
|
7463
|
+
else if (recorded !== current) return false;
|
|
7464
|
+
}
|
|
7465
|
+
return unknown ? void 0 : true;
|
|
7283
7466
|
}
|
|
7284
7467
|
//#endregion
|
|
7285
7468
|
//#region src/dsl/voiceover-actions.ts
|
|
7286
|
-
function
|
|
7469
|
+
function matchesPlainPhoneticOrigin(s, id, scriptId, segmentId) {
|
|
7470
|
+
const phonetic = s["phonetic-script-entities"][id];
|
|
7471
|
+
const run = s.phoneticText[id];
|
|
7472
|
+
const segment = s["audio-script-entities"][scriptId]?.segments.find((item) => item.id === segmentId);
|
|
7473
|
+
return phonetic?.phoneme.audioScriptId === scriptId && phonetic.phoneme.segmentId === segmentId && phonetic.phoneme.phonemeScript === void 0 && run?.start === 0 && run.end === segment?.text.length;
|
|
7474
|
+
}
|
|
7475
|
+
function voiceoverPlan(s, input, result, plainPhoneticIds) {
|
|
7287
7476
|
if (requireTrack(s, input.trackId).role !== "speech" || input.media.kind !== "voiceover-recording") throw new Error("Voiceover requires a speech Track and a recording");
|
|
7288
7477
|
if (input.captions.length && (!input.captionTrackId || requireTrack(s, input.captionTrackId).role !== "caption")) throw new Error("Caption Track is required");
|
|
7289
7478
|
const existing = s["clip-entities"][result.clipId];
|
|
@@ -7322,15 +7511,41 @@ function voiceoverPlan(s, input, result) {
|
|
|
7322
7511
|
inMs: 0,
|
|
7323
7512
|
outMs: input.media.durationMs
|
|
7324
7513
|
}, input.media.durationMs);
|
|
7325
|
-
mutations.push({
|
|
7514
|
+
if (phoneticScriptId !== void 0) mutations.push({
|
|
7326
7515
|
type: "set-relation",
|
|
7327
7516
|
table: "derived-from-relations",
|
|
7328
7517
|
value: {
|
|
7329
7518
|
entityId: result.mediaId,
|
|
7330
|
-
sourceId: phoneticScriptId
|
|
7519
|
+
sourceId: phoneticScriptId,
|
|
7331
7520
|
...input.sourceDigest === void 0 ? {} : { sourceDigest: input.sourceDigest }
|
|
7332
7521
|
}
|
|
7333
7522
|
});
|
|
7523
|
+
else {
|
|
7524
|
+
if (input.sourceDigest !== void 0) throw new Error("A synthesis digest requires its PhoneticScript");
|
|
7525
|
+
if (plainPhoneticIds.length !== segmentIds.length) throw new Error("Expected one PhoneticScript id per plain segment");
|
|
7526
|
+
segmentIds.forEach((segmentId, i) => {
|
|
7527
|
+
const id = plainPhoneticIds[i];
|
|
7528
|
+
if (s["phonetic-script-entities"][id]) {
|
|
7529
|
+
if (!matchesPlainPhoneticOrigin(s, id, result.audioScriptId, segmentId)) throw new Error("PhoneticScript id already names another synthesis origin");
|
|
7530
|
+
} else mutations.push({
|
|
7531
|
+
type: "create-entity",
|
|
7532
|
+
table: "phonetic-script-entities",
|
|
7533
|
+
id,
|
|
7534
|
+
value: { phoneme: {
|
|
7535
|
+
audioScriptId: result.audioScriptId,
|
|
7536
|
+
segmentId
|
|
7537
|
+
} }
|
|
7538
|
+
});
|
|
7539
|
+
if (!Object.values(s["derived-from-relations"]).some((origin) => origin.entityId === result.mediaId && origin.sourceId === id)) mutations.push({
|
|
7540
|
+
type: "set-relation",
|
|
7541
|
+
table: "derived-from-relations",
|
|
7542
|
+
value: {
|
|
7543
|
+
entityId: result.mediaId,
|
|
7544
|
+
sourceId: id
|
|
7545
|
+
}
|
|
7546
|
+
});
|
|
7547
|
+
});
|
|
7548
|
+
}
|
|
7334
7549
|
if (input.voice) {
|
|
7335
7550
|
const id = resourceId("voice", input.voice.system, input.voice.key);
|
|
7336
7551
|
mutations.push({
|
|
@@ -7827,10 +8042,20 @@ var DslEditor = class {
|
|
|
7827
8042
|
captionClipIds: input.captions.map((c) => c.clipId ?? this.idFactory("clip")),
|
|
7828
8043
|
captionIds: input.captions.map(() => this.idFactory("caption"))
|
|
7829
8044
|
};
|
|
8045
|
+
const state = this.dsl.getSnapshot();
|
|
8046
|
+
const plainSegments = input.script.segments === void 0 ? state["audio-script-entities"][result.audioScriptId]?.segments.map((segment) => segment.id) ?? [] : result.segmentIds;
|
|
8047
|
+
const plainPhoneticIds = input.script.phoneticScriptId === void 0 ? plainSegments.map((segmentId) => {
|
|
8048
|
+
const origins = Object.values(state["derived-from-relations"]).filter((origin) => {
|
|
8049
|
+
if (origin.entityId !== result.mediaId) return false;
|
|
8050
|
+
return matchesPlainPhoneticOrigin(state, origin.sourceId, result.audioScriptId, segmentId);
|
|
8051
|
+
});
|
|
8052
|
+
if (origins.length > 1) throw new Error("Ambiguous plain recording origin");
|
|
8053
|
+
return origins[0]?.sourceId ?? this.idFactory("phonetic-script");
|
|
8054
|
+
}) : [];
|
|
7830
8055
|
this.write("applyVoiceover", {
|
|
7831
8056
|
input,
|
|
7832
8057
|
result
|
|
7833
|
-
}, voiceoverPlan(
|
|
8058
|
+
}, voiceoverPlan(state, input, result, plainPhoneticIds), options);
|
|
7834
8059
|
return result;
|
|
7835
8060
|
}
|
|
7836
8061
|
voiceoverScriptId(script) {
|
|
@@ -7847,8 +8072,12 @@ var DslEditor = class {
|
|
|
7847
8072
|
* it; an unchanged one keeps its file. Recordings already spoken from it keep their derived-from.
|
|
7848
8073
|
*/
|
|
7849
8074
|
async setPhoneticScript(input, options) {
|
|
8075
|
+
this.dsl.assertWritable();
|
|
7850
8076
|
const id = input.phoneticScriptId ?? this.idFactory("phonetic-script");
|
|
7851
8077
|
const range = segmentRange(input);
|
|
8078
|
+
assertId(id);
|
|
8079
|
+
assertSegmentRange(range, requireSegment(this.dsl.getSnapshot(), range.audioScriptId, range.segmentId).text);
|
|
8080
|
+
assertPhonemeScript(input.phonemeScript);
|
|
7852
8081
|
const file = input.phonemeScript === void 0 ? {} : await this.fileFor(id, input.phonemeScript);
|
|
7853
8082
|
const exists = Object.hasOwn(this.dsl.getSnapshot()["phonetic-script-entities"], id);
|
|
7854
8083
|
const cleared = {
|
|
@@ -8072,7 +8301,8 @@ function toVideoDraft(snapshot) {
|
|
|
8072
8301
|
} }
|
|
8073
8302
|
} };
|
|
8074
8303
|
} else if (track.role === "speech") {
|
|
8075
|
-
if (!source || source.kind !== "
|
|
8304
|
+
if (!source || source.kind !== "audio" && source.kind !== "voiceover-recording") fail("Speech playback requires an Audio or VoiceoverRecording");
|
|
8305
|
+
if (source.system !== "memota" && !(source.kind === "voiceover-recording" && source.system === "memota-speech")) fail("Speech playback requires a Memota audio file");
|
|
8076
8306
|
requireWholeAudio(source, clip, fail);
|
|
8077
8307
|
if (clip.fillTimeline) fail("Legacy Speech cannot express timeline fill");
|
|
8078
8308
|
const voices = Object.values(snapshot["voice-timbre-relations"]).filter((r) => r.recordingId === sourceId).flatMap((r) => snapshot["voice-entities"][r.voiceId] ? [snapshot["voice-entities"][r.voiceId]] : []);
|
|
@@ -8089,7 +8319,7 @@ function toVideoDraft(snapshot) {
|
|
|
8089
8319
|
audio_storage_key: source.storageKey,
|
|
8090
8320
|
origin_speech_id: source.key,
|
|
8091
8321
|
volume: clip.volume,
|
|
8092
|
-
audio_script: spokenText(snapshot, sourceId),
|
|
8322
|
+
audio_script: source.kind === "voiceover-recording" ? spokenText(snapshot, sourceId) : "",
|
|
8093
8323
|
voice: voice ? {
|
|
8094
8324
|
id: voice.key,
|
|
8095
8325
|
name: voice.name
|
|
@@ -8185,6 +8415,15 @@ function spokenText(snapshot, recordingId) {
|
|
|
8185
8415
|
if (!first) return "";
|
|
8186
8416
|
if (first.phoneticScriptId !== void 0) return snapshot.phoneticText[first.phoneticScriptId]?.text ?? "";
|
|
8187
8417
|
const script = snapshot["audio-script-entities"][first.audioScriptId];
|
|
8418
|
+
if (first.phoneticScriptIds !== void 0 && script) {
|
|
8419
|
+
const ids = first.phoneticScriptIds;
|
|
8420
|
+
const selections = ids.map((id) => snapshot["phonetic-script-entities"][id]);
|
|
8421
|
+
if (!(ids.length === script.segments.length && script.segments.every((segment) => {
|
|
8422
|
+
const matching = ids.filter((id) => snapshot["phonetic-script-entities"][id]?.phoneme.segmentId === segment.id);
|
|
8423
|
+
const run = matching.length === 1 ? snapshot.phoneticText[matching[0]] : void 0;
|
|
8424
|
+
return run?.start === 0 && run.end === segment.text.length;
|
|
8425
|
+
})) || selections.some((selection) => selection.phoneme.phonemeScript !== void 0)) projectionError("ambiguous-script", `media-entities/${recordingId}`, "Multiple phonetic sources require complete plain selections of one script");
|
|
8426
|
+
}
|
|
8188
8427
|
return script ? script.segments.map((segment) => segment.text).join("\n") : "";
|
|
8189
8428
|
}
|
|
8190
8429
|
//#endregion
|
|
@@ -8524,4 +8763,4 @@ function importedScript(id, text, mintSegment) {
|
|
|
8524
8763
|
};
|
|
8525
8764
|
}
|
|
8526
8765
|
//#endregion
|
|
8527
|
-
export {
|
|
8766
|
+
export { DslProjectionError as _, DslEditor as a, resourceId as b, UnsupportedDocumentFormatError as c, documentFormat as d, medeoDslLoroShape as f, resolveDslLayout as g, timeAnchorCodec as h, createInitialMedeoDsl as i, assertDslDocument as l, isInvalidAtomic as m, toVideoDraft as n, toAudioScriptViews as o, captionContentCodec as p, DEFAULT_UNIT_TIME_MS as r, MedeoDsl as s, fromVideoDraft as t, assertPhoneticBindingFormat as u, defaultIdFactory as v, tupleHash as x, relationId as y };
|