@mengine/medeo-client 3.0.1-dsl.4 → 3.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/README.md +1 -5
- package/dist/{dsl-DgUxcAg3.js → dsl-C0-G8JVT.js} +251 -333
- package/dist/dsl.d.ts +2 -2
- package/dist/dsl.js +2 -2
- package/dist/{index-D1EvUTdG.d.ts → index-BOPcHqIj.d.ts} +48 -30
- package/dist/{index-iEPbMfoP.d.ts → index-C_wwGt5k.d.ts} +3 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -16
- package/dist/testing.d.ts +1 -2
- package/dist/testing.js +14 -35
- package/package.json +6 -8
- package/dist/schemas.d.ts +0 -606
- package/dist/schemas.js +0 -478
|
@@ -145,12 +145,8 @@ function assertCaptionContent(value) {
|
|
|
145
145
|
}
|
|
146
146
|
const phonemeFields = {
|
|
147
147
|
...selectionFields,
|
|
148
|
-
phonemeScript: optional$1(
|
|
148
|
+
phonemeScript: optional$1(string)
|
|
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
|
-
}
|
|
154
150
|
function assertPhoneme(value) {
|
|
155
151
|
if (!segmentSelection(phonemeFields)(value)) throw new Error("Invalid phoneme");
|
|
156
152
|
}
|
|
@@ -173,9 +169,39 @@ function assertScriptSegment(value) {
|
|
|
173
169
|
/** A script's segments: each with its own id, unique within the script. */
|
|
174
170
|
const segments = (v) => Array.isArray(v) && v.every(segment) && new Set(v.map((s) => s.id)).size === v.length;
|
|
175
171
|
const locator = {
|
|
176
|
-
system: required(oneOf("memota", "memota-speech")),
|
|
172
|
+
system: required(oneOf("memota", "memota-speech", "placeholder")),
|
|
177
173
|
key: required(nonempty)
|
|
178
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* A placeholder medium is an Image with no file, standing in where a legacy VideoDraft placed a
|
|
177
|
+
* visual clip the old editor drew as a blank (D196). The blank one stands in for a clip placed
|
|
178
|
+
* without media (`origin_media_id: ''`); one keyed by a memota media id stands in for media the
|
|
179
|
+
* clip named but Director found missing, so the id survives. Importing keeps the clips' time on
|
|
180
|
+
* them instead of dropping them. Only migration authors them.
|
|
181
|
+
*/
|
|
182
|
+
const BLANK_PLACEHOLDER = {
|
|
183
|
+
system: "placeholder",
|
|
184
|
+
key: "blank",
|
|
185
|
+
kind: "image"
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Only a memota media id may key a missing-media placeholder: visual parts name media and nothing
|
|
189
|
+
* else, and a key of this shape can never be mistaken for a file.
|
|
190
|
+
*/
|
|
191
|
+
const MISSING_MEDIA_KEY = /^media_[0-9A-Za-z]+$/;
|
|
192
|
+
/** The placeholder for a memota media id Director found missing. */
|
|
193
|
+
function missingMediaPlaceholder(mediaId) {
|
|
194
|
+
return {
|
|
195
|
+
system: "placeholder",
|
|
196
|
+
key: mediaId,
|
|
197
|
+
kind: "image"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function assertPlaceholder(media) {
|
|
201
|
+
if (media.system !== "placeholder") return;
|
|
202
|
+
if (media.kind !== "image" || media.key !== BLANK_PLACEHOLDER.key && !(typeof media.key === "string" && MISSING_MEDIA_KEY.test(media.key))) throw new Error("A placeholder is the blank Image or one standing in for a missing memota media id");
|
|
203
|
+
if (media.storageKey !== void 0 || media.digest !== void 0) throw new Error("A placeholder has no file");
|
|
204
|
+
}
|
|
179
205
|
const entityFields = {
|
|
180
206
|
"timeline-entities": {},
|
|
181
207
|
"track-entities": {
|
|
@@ -271,6 +297,7 @@ function assertEntity(table, value) {
|
|
|
271
297
|
checkFields(value, entityFields[table]);
|
|
272
298
|
if (table === "audio-script-entities") assertTranscriptFile(value);
|
|
273
299
|
if (table === "media-entities") {
|
|
300
|
+
assertPlaceholder(value);
|
|
274
301
|
if (value.kind === "image" && value.durationMs !== void 0) throw new Error("Image has no intrinsic duration");
|
|
275
302
|
if (value.kind !== "image" && !positive(value.durationMs)) throw new Error("Finite media requires durationMs");
|
|
276
303
|
if (["audio", "voiceover-recording"].includes(value.kind) && !nonempty(value.storageKey)) throw new Error("Audio requires storageKey");
|
|
@@ -299,6 +326,7 @@ function assertEntityPatch(table, patch, current) {
|
|
|
299
326
|
]) if (Object.hasOwn(patch, key) && patch[key] !== current[key]) throw new Error(`Resource identity field is immutable: ${key}`);
|
|
300
327
|
}
|
|
301
328
|
if (table === "media-entities") {
|
|
329
|
+
assertPlaceholder(next);
|
|
302
330
|
if (Object.hasOwn(patch, "durationMs")) {
|
|
303
331
|
if (next.kind === "image" ? next.durationMs !== void 0 : !positive(next.durationMs)) throw new Error("durationMs must match the media kind");
|
|
304
332
|
}
|
|
@@ -642,63 +670,6 @@ function assertContainTree(rows, tree) {
|
|
|
642
670
|
}
|
|
643
671
|
}
|
|
644
672
|
//#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
|
|
702
673
|
//#region ../medeo-dsl/src/plan.ts
|
|
703
674
|
/** Include removed nested keys: a partial merge alone would conceal a deleted required field. */
|
|
704
675
|
function replacementPatch(before, after) {
|
|
@@ -714,7 +685,6 @@ function replacementPatch(before, after) {
|
|
|
714
685
|
*/
|
|
715
686
|
function assertChangeAdmitted(change) {
|
|
716
687
|
const { before, after } = change;
|
|
717
|
-
const parents = change.containment ? clipParents(change.containment.after) : void 0;
|
|
718
688
|
for (const [table, rows] of change.entities) for (const [id, fields] of rows) {
|
|
719
689
|
const value = after[table][id];
|
|
720
690
|
if (value === void 0) continue;
|
|
@@ -729,17 +699,25 @@ function assertChangeAdmitted(change) {
|
|
|
729
699
|
}
|
|
730
700
|
const row = value;
|
|
731
701
|
for (const field of referenceFields[table] ?? []) if (!old || fields.has(field)) assertReferenceAdmitted(after, table, id, field, row[field]);
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
702
|
+
const marker = row.seqMarker;
|
|
703
|
+
if (table === "clip-entities" && (!old || fields.has("seqMarker")) && marker?.outMs <= marker?.inMs) throw new Error("Clip window must be nonempty");
|
|
704
|
+
if (table === "clip-entities" && (!old || fields.has("seqMarker") || fields.has("source"))) assertFillsWithWholeSource(after, marker, row.source);
|
|
736
705
|
}
|
|
737
706
|
if (change.entities.has("timeline-entities") && Object.keys(after["timeline-entities"]).length > 1) throw new Error("Document already has a Timeline");
|
|
738
707
|
for (const [table, ids] of change.relations) for (const id of ids) {
|
|
739
708
|
const value = after[table][id];
|
|
740
709
|
if (value !== void 0) assertRelationAdmitted(after, table, id, value);
|
|
741
710
|
}
|
|
742
|
-
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* A Clip that fills the Timeline plays its whole source at 1×: the player loops a short source and
|
|
714
|
+
* cuts a long one at the Timeline's end (RFC 02 §3.3), so a window or speed of its own has no meaning.
|
|
715
|
+
*/
|
|
716
|
+
function assertFillsWithWholeSource(rows, marker, source) {
|
|
717
|
+
if (marker?.fillTimeline !== true) return;
|
|
718
|
+
const media = typeof source === "string" ? rows["media-entities"][source] : void 0;
|
|
719
|
+
const durationMs = typeof media?.durationMs === "number" ? media.durationMs : void 0;
|
|
720
|
+
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`);
|
|
743
721
|
}
|
|
744
722
|
//#endregion
|
|
745
723
|
//#region src/dsl/projection/types.ts
|
|
@@ -975,11 +953,15 @@ function requireSource(s, id, role) {
|
|
|
975
953
|
return media;
|
|
976
954
|
}
|
|
977
955
|
function assertMediaRole(media, role) {
|
|
978
|
-
if (!media
|
|
979
|
-
|
|
956
|
+
if (!media || !{
|
|
957
|
+
video_clip: ["video", "image"],
|
|
958
|
+
speech: ["audio", "voiceover-recording"],
|
|
959
|
+
bgm: ["audio"],
|
|
960
|
+
caption: []
|
|
961
|
+
}[role].includes(media.kind)) throw new Error(`Source is not allowed on ${role} Track`);
|
|
980
962
|
}
|
|
981
963
|
function validateWindow(window, duration) {
|
|
982
|
-
|
|
964
|
+
if (!Number.isSafeInteger(window.inMs) || !Number.isSafeInteger(window.outMs) || window.inMs < 0 || window.outMs <= window.inMs || duration !== void 0 && window.outMs > duration) throw new Error("Invalid Clip window");
|
|
983
965
|
}
|
|
984
966
|
function trackOf(s, id) {
|
|
985
967
|
requireClip(s, id);
|
|
@@ -1267,15 +1249,14 @@ function replaceSourcePlan(s, clipId, input) {
|
|
|
1267
1249
|
patch: {
|
|
1268
1250
|
seqMarker: {
|
|
1269
1251
|
inMs: input.inMs,
|
|
1270
|
-
outMs: input.outMs
|
|
1271
|
-
...supportsClipSpeed(source.source) ? {} : { rate: void 0 }
|
|
1252
|
+
outMs: input.outMs
|
|
1272
1253
|
},
|
|
1273
1254
|
source: source.id
|
|
1274
1255
|
}
|
|
1275
1256
|
}];
|
|
1276
1257
|
}
|
|
1277
1258
|
//#endregion
|
|
1278
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1259
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/schema/resolver.js
|
|
1279
1260
|
function getMapFieldSchema(schema, key) {
|
|
1280
1261
|
if (!schema) return void 0;
|
|
1281
1262
|
if (Object.prototype.hasOwnProperty.call(schema.definition, key)) return schema.definition[key];
|
|
@@ -1305,10 +1286,10 @@ function getChildContainerSchema(schema, childKey) {
|
|
|
1305
1286
|
}
|
|
1306
1287
|
}
|
|
1307
1288
|
//#endregion
|
|
1308
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1289
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/constants.js
|
|
1309
1290
|
const CID_KEY = "$cid";
|
|
1310
1291
|
//#endregion
|
|
1311
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1292
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/container-inference.js
|
|
1312
1293
|
function isPlainObjectValue(value) {
|
|
1313
1294
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp) && typeof value !== "function";
|
|
1314
1295
|
}
|
|
@@ -1347,7 +1328,7 @@ function getContainerValueKind(value) {
|
|
|
1347
1328
|
return "other";
|
|
1348
1329
|
}
|
|
1349
1330
|
//#endregion
|
|
1350
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1331
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/utils.js
|
|
1351
1332
|
/**
|
|
1352
1333
|
* Utility functions for Loro Mirror core
|
|
1353
1334
|
*/
|
|
@@ -1771,7 +1752,7 @@ function safeStringify(value, indent = 2) {
|
|
|
1771
1752
|
return JSON.stringify(replacer(value), null, indent);
|
|
1772
1753
|
}
|
|
1773
1754
|
//#endregion
|
|
1774
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
1755
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/schema/validators.js
|
|
1775
1756
|
const schemaValidationCache = /* @__PURE__ */ new WeakMap();
|
|
1776
1757
|
function isCacheableValue(value) {
|
|
1777
1758
|
return typeof value === "object" && value !== null;
|
|
@@ -2051,7 +2032,7 @@ function getDefaultValue(schema) {
|
|
|
2051
2032
|
}
|
|
2052
2033
|
}
|
|
2053
2034
|
//#endregion
|
|
2054
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2035
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/schema/index.js
|
|
2055
2036
|
/**
|
|
2056
2037
|
* Create a schema definition
|
|
2057
2038
|
*/
|
|
@@ -2816,7 +2797,7 @@ const produce = new Immer2({
|
|
|
2816
2797
|
useStrictShallowCopy: true
|
|
2817
2798
|
}).produce;
|
|
2818
2799
|
//#endregion
|
|
2819
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2800
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/debounce-timer.js
|
|
2820
2801
|
/**
|
|
2821
2802
|
* Deadline-based debounce timer.
|
|
2822
2803
|
*
|
|
@@ -2874,7 +2855,7 @@ var DebounceTimer = class {
|
|
|
2874
2855
|
}
|
|
2875
2856
|
};
|
|
2876
2857
|
//#endregion
|
|
2877
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
2858
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/ephemeral.js
|
|
2878
2859
|
/**
|
|
2879
2860
|
* Manages ephemeral patches: storage, composition, path resolution, and finalization.
|
|
2880
2861
|
*/
|
|
@@ -3149,7 +3130,7 @@ var EphemeralPatchManager = class {
|
|
|
3149
3130
|
}
|
|
3150
3131
|
};
|
|
3151
3132
|
//#endregion
|
|
3152
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3133
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/tree-utils.js
|
|
3153
3134
|
function normalizeTreeJson(input, options) {
|
|
3154
3135
|
if (!Array.isArray(input)) return [];
|
|
3155
3136
|
return input.map((node) => normalizeTreeNode(node, options));
|
|
@@ -3172,7 +3153,7 @@ function isPlainRecord(value) {
|
|
|
3172
3153
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3173
3154
|
}
|
|
3174
3155
|
//#endregion
|
|
3175
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3156
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/loroEventApply.js
|
|
3176
3157
|
function isJSONObject(v) {
|
|
3177
3158
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3178
3159
|
}
|
|
@@ -3594,7 +3575,7 @@ function isIgnoredByAncestor(id, ignoreSet, getContainerById) {
|
|
|
3594
3575
|
return false;
|
|
3595
3576
|
}
|
|
3596
3577
|
//#endregion
|
|
3597
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
3578
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/diff.js
|
|
3598
3579
|
/**
|
|
3599
3580
|
* Finds the longest increasing subsequence of a sequence of numbers
|
|
3600
3581
|
* @param sequence The sequence of numbers
|
|
@@ -4325,7 +4306,7 @@ function diffMap(doc, oldState, newState, containerId, schema, inferOptions) {
|
|
|
4325
4306
|
return changes;
|
|
4326
4307
|
}
|
|
4327
4308
|
//#endregion
|
|
4328
|
-
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.
|
|
4309
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.4/node_modules/loro-mirror/dist/core/mirror.js
|
|
4329
4310
|
/**
|
|
4330
4311
|
* Mirror core functionality for bidirectional sync between app state and Loro CRDT
|
|
4331
4312
|
*/
|
|
@@ -5896,30 +5877,6 @@ const phonemeCodec = codec((v) => {
|
|
|
5896
5877
|
assertPhoneme(value);
|
|
5897
5878
|
return value;
|
|
5898
5879
|
});
|
|
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
|
-
});
|
|
5923
5880
|
/** A Clip's sequence marker is stored as one string, so it is taken or replaced whole. */
|
|
5924
5881
|
const seqMarkerCodec = codec((v) => {
|
|
5925
5882
|
assertSeqMarker(v);
|
|
@@ -5986,7 +5943,13 @@ const medeoDslLoroShape = schema({
|
|
|
5986
5943
|
storageKey: schema.String(optional),
|
|
5987
5944
|
digest: schema.String(optional)
|
|
5988
5945
|
})),
|
|
5989
|
-
"phonetic-script-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
5946
|
+
"phonetic-script-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
5947
|
+
phoneme: atomicValue(phonemeCodec),
|
|
5948
|
+
system: schema.String(optional),
|
|
5949
|
+
key: schema.String(optional),
|
|
5950
|
+
storageKey: schema.String(optional),
|
|
5951
|
+
digest: schema.String(optional)
|
|
5952
|
+
})),
|
|
5990
5953
|
"caption-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
5991
5954
|
selection: optionalAtomicValue(captionContentCodec),
|
|
5992
5955
|
text: schema.LoroText(optional),
|
|
@@ -6038,78 +6001,20 @@ function documentFormat(doc) {
|
|
|
6038
6001
|
}
|
|
6039
6002
|
var UnsupportedDocumentFormatError = class extends Error {
|
|
6040
6003
|
code = "unsupported_document_format";
|
|
6041
|
-
constructor(
|
|
6042
|
-
super(
|
|
6004
|
+
constructor() {
|
|
6005
|
+
super("Expected a new DSL document; legacy, mixed or uninitialized content is not supported");
|
|
6043
6006
|
this.name = "UnsupportedDocumentFormatError";
|
|
6044
6007
|
}
|
|
6045
6008
|
};
|
|
6046
6009
|
/** Run after initial import, before opening an editable handle. Empty new documents still have one Timeline. */
|
|
6047
6010
|
function assertDslDocument(doc) {
|
|
6048
6011
|
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
|
-
}
|
|
6059
6012
|
}
|
|
6060
6013
|
//#endregion
|
|
6061
6014
|
//#region ../medeo-dsl/src/schema/text-styles.ts
|
|
6062
6015
|
/** How rich-text marks extend at their edges: a language runs on into text typed after it. */
|
|
6063
6016
|
const textStyles = { language: { expand: "after" } };
|
|
6064
6017
|
//#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
|
|
6113
6018
|
//#region src/dsl/document/read.ts
|
|
6114
6019
|
/** Looking up a missing root must not create it. */
|
|
6115
6020
|
function rootMap(doc, name) {
|
|
@@ -6193,7 +6098,6 @@ function referenceTargets(table, row) {
|
|
|
6193
6098
|
}
|
|
6194
6099
|
}
|
|
6195
6100
|
function readMedeoDsl(doc, state) {
|
|
6196
|
-
const logical = logicalDslState(state);
|
|
6197
6101
|
const diagnostics = [];
|
|
6198
6102
|
const diagnostic = (code, path, error) => diagnostics.push({
|
|
6199
6103
|
code,
|
|
@@ -6203,7 +6107,7 @@ function readMedeoDsl(doc, state) {
|
|
|
6203
6107
|
const entities = Object.fromEntries(entityTables.map((t) => [t, {}]));
|
|
6204
6108
|
const relations = Object.fromEntries(relationTables.map((t) => [t, {}]));
|
|
6205
6109
|
const richText = {};
|
|
6206
|
-
for (const table of entityTables) for (const [id, raw] of Object.entries(
|
|
6110
|
+
for (const table of entityTables) for (const [id, raw] of Object.entries(state[table] ?? {})) try {
|
|
6207
6111
|
assertId(id);
|
|
6208
6112
|
let v = raw;
|
|
6209
6113
|
for (const field of optionalReferences[table] ?? []) {
|
|
@@ -6709,11 +6613,7 @@ function validateDraft(tracked) {
|
|
|
6709
6613
|
before,
|
|
6710
6614
|
after: state,
|
|
6711
6615
|
entities: tracked.entities,
|
|
6712
|
-
relations: tracked.relations
|
|
6713
|
-
containment: {
|
|
6714
|
-
before: plainTree(before["contain-relations"] ?? []),
|
|
6715
|
-
after: plainTree(state["contain-relations"] ?? [])
|
|
6716
|
-
}
|
|
6616
|
+
relations: tracked.relations
|
|
6717
6617
|
});
|
|
6718
6618
|
if (!tracked.treeChanged) return;
|
|
6719
6619
|
const oldNodes = nodeIndex(before["contain-relations"] ?? []);
|
|
@@ -6790,11 +6690,8 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6790
6690
|
const steps = [];
|
|
6791
6691
|
const newCaptionTexts = /* @__PURE__ */ new Map();
|
|
6792
6692
|
const bodies = /* @__PURE__ */ new Map();
|
|
6793
|
-
const
|
|
6794
|
-
|
|
6795
|
-
const phoneticRows = /* @__PURE__ */ new Map();
|
|
6796
|
-
mirror.setState((native) => {
|
|
6797
|
-
const raw = logicalDslState(native);
|
|
6693
|
+
const before = mirror.getState();
|
|
6694
|
+
mirror.setState((raw) => {
|
|
6798
6695
|
const tracked = new TransactionDraft(raw, before);
|
|
6799
6696
|
setReject((error) => tracked.fail(error));
|
|
6800
6697
|
/** The current text of a Caption's own Text or a script segment, as this transaction leaves it so far. */
|
|
@@ -6986,15 +6883,6 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
6986
6883
|
for (const step of steps) if (step.type === "text") if ("captionId" in step.target) requireEntity(raw, "caption-entities", step.target.captionId);
|
|
6987
6884
|
else requireSegment(raw, step.target.audioScriptId, step.target.segmentId);
|
|
6988
6885
|
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
|
-
}
|
|
6998
6886
|
for (const [id, fields] of tracked.entities.get("caption-entities") ?? []) {
|
|
6999
6887
|
const row = raw["caption-entities"][id];
|
|
7000
6888
|
if (fields.has("text") && row?.text !== void 0 && before["caption-entities"][id]?.text === void 0) {
|
|
@@ -7003,7 +6891,6 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
7003
6891
|
}
|
|
7004
6892
|
}
|
|
7005
6893
|
for (const table of Object.keys(selectionField)) {
|
|
7006
|
-
if (table === "phonetic-script-entities") continue;
|
|
7007
6894
|
const field = selectionField[table];
|
|
7008
6895
|
for (const [id, fields] of tracked.entities.get(table) ?? []) {
|
|
7009
6896
|
const row = raw[table][id];
|
|
@@ -7043,8 +6930,6 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
7043
6930
|
applyTextEdits(text, step.edits);
|
|
7044
6931
|
continue;
|
|
7045
6932
|
}
|
|
7046
|
-
const phonetic = step.table === "phonetic-script-entities" ? phoneticRows.get(step.id) : void 0;
|
|
7047
|
-
if (step.table === "phonetic-script-entities" && !phonetic) continue;
|
|
7048
6933
|
const row = entityRow(doc, step.table, step.id);
|
|
7049
6934
|
const field = selectionField[step.table];
|
|
7050
6935
|
if (step.type === "range") {
|
|
@@ -7063,21 +6948,12 @@ function runTransaction(doc, mirror, edit, options, setReject) {
|
|
|
7063
6948
|
if (!value.start || !value.end) throw new Error("Unable to construct prepared range");
|
|
7064
6949
|
}
|
|
7065
6950
|
if (step.phonemeScript !== void 0) value.phonemeScript = step.phonemeScript;
|
|
7066
|
-
if (
|
|
7067
|
-
|
|
7068
|
-
|
|
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 {
|
|
6951
|
+
if (!row) throw new Error("Unable to construct prepared range");
|
|
6952
|
+
row.set(field, codecs[step.table].encode(value));
|
|
6953
|
+
} else if (step.value !== void 0) {
|
|
7073
6954
|
if (!row) throw new Error("Prepared selection owner is missing");
|
|
7074
6955
|
row.set(field, codecs[step.table].encode(step.value));
|
|
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));
|
|
6956
|
+
} else if (row?.get(field) !== void 0) row.delete(field);
|
|
7081
6957
|
}
|
|
7082
6958
|
}
|
|
7083
6959
|
});
|
|
@@ -7124,7 +7000,7 @@ function assertSelectionsAfter(raw, before, tracked, steps) {
|
|
|
7124
7000
|
if (!row) continue;
|
|
7125
7001
|
const phoneme = phonemes.has(id) ? phonemes.get(id) : row.phoneme;
|
|
7126
7002
|
const old = before["phonetic-script-entities"][id];
|
|
7127
|
-
assertPhoneticScript(old
|
|
7003
|
+
assertPhoneticScript(old, {
|
|
7128
7004
|
...row,
|
|
7129
7005
|
phoneme: isRecord(phoneme) ? phoneme : void 0
|
|
7130
7006
|
});
|
|
@@ -7163,7 +7039,6 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7163
7039
|
"tracks",
|
|
7164
7040
|
"part_library"
|
|
7165
7041
|
].some((key) => Object.hasOwn(roots, key))) throw new Error("Expected a new DSL document, not a legacy content model");
|
|
7166
|
-
assertPhoneticBindingFormat(doc);
|
|
7167
7042
|
doc.configTextStyle(textStyles);
|
|
7168
7043
|
this.mirror = new Mirror({
|
|
7169
7044
|
doc,
|
|
@@ -7227,11 +7102,6 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7227
7102
|
editCaptionText(captionId, expectedBodyId, edits, audit) {
|
|
7228
7103
|
this.transact((tx) => tx.editCaptionText(captionId, edits, expectedBodyId), audit);
|
|
7229
7104
|
}
|
|
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
|
-
}
|
|
7235
7105
|
/**
|
|
7236
7106
|
* A run inside one segment as native Cursors. Local ranges exclude insertions at their start and
|
|
7237
7107
|
* include insertions at their end. K01 remains deferred.
|
|
@@ -7264,7 +7134,13 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7264
7134
|
if (this.ownsDoc) this.doc.free();
|
|
7265
7135
|
}
|
|
7266
7136
|
mutate(run) {
|
|
7267
|
-
this.
|
|
7137
|
+
this.assertActive();
|
|
7138
|
+
if (this.writing || this.notifying) {
|
|
7139
|
+
const error = /* @__PURE__ */ new Error("A DSL mutation is already running");
|
|
7140
|
+
this.rejectTransaction?.(error);
|
|
7141
|
+
throw error;
|
|
7142
|
+
}
|
|
7143
|
+
if (this.doc.getPendingTxnLength() !== 0) throw new Error("Commit external native edits before a DSL mutation");
|
|
7268
7144
|
const apply = () => {
|
|
7269
7145
|
this.writing = true;
|
|
7270
7146
|
try {
|
|
@@ -7279,15 +7155,6 @@ var MedeoDsl = class MedeoDsl {
|
|
|
7279
7155
|
if (this.options.mutationGuard) this.options.mutationGuard.run(apply);
|
|
7280
7156
|
else apply();
|
|
7281
7157
|
}
|
|
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
|
-
}
|
|
7291
7158
|
notify() {
|
|
7292
7159
|
if (this.disposed || !this.dirty) return;
|
|
7293
7160
|
this.dirty = false;
|
|
@@ -7314,13 +7181,12 @@ function spokenScripts(snapshot, recordingId) {
|
|
|
7314
7181
|
const found = /* @__PURE__ */ new Map();
|
|
7315
7182
|
const seen = /* @__PURE__ */ new Set();
|
|
7316
7183
|
const visit = (id, phoneticScriptId) => {
|
|
7184
|
+
if (seen.has(id)) return;
|
|
7185
|
+
seen.add(id);
|
|
7317
7186
|
if (snapshot["audio-script-entities"][id]) {
|
|
7318
|
-
if (!found.has(id)) found.set(id,
|
|
7319
|
-
if (phoneticScriptId !== void 0) found.get(id).add(phoneticScriptId);
|
|
7187
|
+
if (!found.has(id)) found.set(id, phoneticScriptId);
|
|
7320
7188
|
return;
|
|
7321
7189
|
}
|
|
7322
|
-
if (seen.has(id)) return;
|
|
7323
|
-
seen.add(id);
|
|
7324
7190
|
const phonetic = snapshot["phonetic-script-entities"][id];
|
|
7325
7191
|
if (phonetic) {
|
|
7326
7192
|
visit(phonetic.phoneme.audioScriptId, id);
|
|
@@ -7330,18 +7196,11 @@ function spokenScripts(snapshot, recordingId) {
|
|
|
7330
7196
|
};
|
|
7331
7197
|
visit(recordingId);
|
|
7332
7198
|
if (!found.size) {
|
|
7333
|
-
for (const r of relations) if (r.sourceId === recordingId && snapshot["audio-script-entities"][r.entityId]) found.set(r.entityId,
|
|
7199
|
+
for (const r of relations) if (r.sourceId === recordingId && snapshot["audio-script-entities"][r.entityId]) found.set(r.entityId, void 0);
|
|
7334
7200
|
}
|
|
7335
|
-
return [...found].map(([audioScriptId,
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
audioScriptId,
|
|
7339
|
-
phoneticScriptId: phoneticScriptIds[0]
|
|
7340
|
-
};
|
|
7341
|
-
return phoneticScriptIds.length ? {
|
|
7342
|
-
audioScriptId,
|
|
7343
|
-
phoneticScriptIds
|
|
7344
|
-
} : { audioScriptId };
|
|
7201
|
+
return [...found].map(([audioScriptId, phoneticScriptId]) => phoneticScriptId === void 0 ? { audioScriptId } : {
|
|
7202
|
+
audioScriptId,
|
|
7203
|
+
phoneticScriptId
|
|
7345
7204
|
});
|
|
7346
7205
|
}
|
|
7347
7206
|
/** Every AudioScript in the document with its PhoneticScripts, the Speech Clips saying it and the Caption Clips showing it. */
|
|
@@ -7386,6 +7245,7 @@ function toAudioScriptViews(snapshot) {
|
|
|
7386
7245
|
});
|
|
7387
7246
|
}
|
|
7388
7247
|
const layout = resolveDslLayout(snapshot);
|
|
7248
|
+
for (const view of views.values()) placeTranscript(snapshot, layout, view);
|
|
7389
7249
|
const timbres = Object.values(snapshot["voice-timbre-relations"]);
|
|
7390
7250
|
for (const track of layout.tracks) for (const { clipId } of track.clips) {
|
|
7391
7251
|
const source = snapshot["clip-entities"][clipId]?.source;
|
|
@@ -7409,7 +7269,6 @@ function toAudioScriptViews(snapshot) {
|
|
|
7409
7269
|
clipId,
|
|
7410
7270
|
recordingId: source,
|
|
7411
7271
|
...spoken.phoneticScriptId === void 0 ? {} : { phoneticScriptId: spoken.phoneticScriptId },
|
|
7412
|
-
...spoken.phoneticScriptIds === void 0 ? {} : { phoneticScriptIds: spoken.phoneticScriptIds },
|
|
7413
7272
|
voices,
|
|
7414
7273
|
...applied === void 0 ? {} : { applied }
|
|
7415
7274
|
});
|
|
@@ -7426,23 +7285,19 @@ function toAudioScriptViews(snapshot) {
|
|
|
7426
7285
|
segment
|
|
7427
7286
|
});
|
|
7428
7287
|
}
|
|
7429
|
-
for (const view of views.values()) placeTranscript(snapshot, layout, view);
|
|
7430
7288
|
return [...views.values()];
|
|
7431
7289
|
}
|
|
7432
7290
|
/**
|
|
7433
7291
|
* A transcript's segments on the Timeline: a segment's time is in the medium the script was read
|
|
7434
|
-
* off
|
|
7435
|
-
* Clip holding that source moment is used. The source time is never taken from the Caption.
|
|
7292
|
+
* off, so it lands where the earliest Clip playing that medium holds that moment in its window.
|
|
7436
7293
|
*/
|
|
7437
7294
|
function placeTranscript(snapshot, layout, view) {
|
|
7438
7295
|
if (!view.sourceMediaIds.length) return;
|
|
7439
7296
|
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);
|
|
7440
|
-
for (const
|
|
7297
|
+
for (const segment of view.segments) {
|
|
7441
7298
|
const at = segment.startMs;
|
|
7442
7299
|
if (at === void 0) continue;
|
|
7443
|
-
const
|
|
7444
|
-
const candidates = plays.filter((c) => c.inMs <= at && at < c.outMs);
|
|
7445
|
-
const clip = candidates.find((c) => anchoredUses.has(c.clipId)) ?? candidates[0];
|
|
7300
|
+
const clip = plays.find((c) => c.inMs <= at && at < c.outMs);
|
|
7446
7301
|
if (clip) segment.timelineMs = Math.round(clip.startMs + (at - clip.inMs) / clip.rate);
|
|
7447
7302
|
}
|
|
7448
7303
|
}
|
|
@@ -7453,26 +7308,14 @@ function placeTranscript(snapshot, layout, view) {
|
|
|
7453
7308
|
* not what a take was synthesized from (D190).
|
|
7454
7309
|
*/
|
|
7455
7310
|
function appliedSpeech(snapshot, recordingId, spoken) {
|
|
7456
|
-
const
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
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;
|
|
7311
|
+
const sourceId = spoken.phoneticScriptId ?? spoken.audioScriptId;
|
|
7312
|
+
const recorded = Object.values(snapshot["derived-from-relations"]).find((r) => r.entityId === recordingId && r.sourceId === sourceId)?.sourceDigest;
|
|
7313
|
+
const current = spoken.phoneticScriptId === void 0 ? void 0 : snapshot["phonetic-script-entities"][sourceId]?.digest;
|
|
7314
|
+
return recorded === void 0 || current === void 0 ? void 0 : recorded === current;
|
|
7466
7315
|
}
|
|
7467
7316
|
//#endregion
|
|
7468
7317
|
//#region src/dsl/voiceover-actions.ts
|
|
7469
|
-
function
|
|
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) {
|
|
7318
|
+
function voiceoverPlan(s, input, result) {
|
|
7476
7319
|
if (requireTrack(s, input.trackId).role !== "speech" || input.media.kind !== "voiceover-recording") throw new Error("Voiceover requires a speech Track and a recording");
|
|
7477
7320
|
if (input.captions.length && (!input.captionTrackId || requireTrack(s, input.captionTrackId).role !== "caption")) throw new Error("Caption Track is required");
|
|
7478
7321
|
const existing = s["clip-entities"][result.clipId];
|
|
@@ -7511,44 +7354,15 @@ function voiceoverPlan(s, input, result, plainPhoneticIds) {
|
|
|
7511
7354
|
inMs: 0,
|
|
7512
7355
|
outMs: input.media.durationMs
|
|
7513
7356
|
}, input.media.durationMs);
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
...input.sourceDigest === void 0 ? {} : { sourceDigest: input.sourceDigest }
|
|
7524
|
-
}
|
|
7525
|
-
});
|
|
7526
|
-
} else {
|
|
7527
|
-
if (input.sourceDigest !== void 0) throw new Error("A synthesis digest requires its PhoneticScript");
|
|
7528
|
-
if (plainPhoneticIds.length !== segmentIds.length) throw new Error("Expected one PhoneticScript id per plain segment");
|
|
7529
|
-
segmentIds.forEach((segmentId, i) => {
|
|
7530
|
-
const id = plainPhoneticIds[i];
|
|
7531
|
-
if (s["phonetic-script-entities"][id]) {
|
|
7532
|
-
if (!matchesPlainPhoneticOrigin(s, id, result.audioScriptId, segmentId)) throw new Error("PhoneticScript id already names another synthesis origin");
|
|
7533
|
-
} else mutations.push({
|
|
7534
|
-
type: "create-entity",
|
|
7535
|
-
table: "phonetic-script-entities",
|
|
7536
|
-
id,
|
|
7537
|
-
value: { phoneme: {
|
|
7538
|
-
audioScriptId: result.audioScriptId,
|
|
7539
|
-
segmentId
|
|
7540
|
-
} }
|
|
7541
|
-
});
|
|
7542
|
-
if (!Object.values(s["derived-from-relations"]).some((origin) => origin.entityId === result.mediaId && origin.sourceId === id)) mutations.push({
|
|
7543
|
-
type: "set-relation",
|
|
7544
|
-
table: "derived-from-relations",
|
|
7545
|
-
value: {
|
|
7546
|
-
entityId: result.mediaId,
|
|
7547
|
-
sourceId: id
|
|
7548
|
-
}
|
|
7549
|
-
});
|
|
7550
|
-
});
|
|
7551
|
-
}
|
|
7357
|
+
mutations.push({
|
|
7358
|
+
type: "set-relation",
|
|
7359
|
+
table: "derived-from-relations",
|
|
7360
|
+
value: {
|
|
7361
|
+
entityId: result.mediaId,
|
|
7362
|
+
sourceId: phoneticScriptId ?? result.audioScriptId,
|
|
7363
|
+
...input.sourceDigest === void 0 ? {} : { sourceDigest: input.sourceDigest }
|
|
7364
|
+
}
|
|
7365
|
+
});
|
|
7552
7366
|
if (input.voice) {
|
|
7553
7367
|
const id = resourceId("voice", input.voice.system, input.voice.key);
|
|
7554
7368
|
mutations.push({
|
|
@@ -8045,20 +7859,10 @@ var DslEditor = class {
|
|
|
8045
7859
|
captionClipIds: input.captions.map((c) => c.clipId ?? this.idFactory("clip")),
|
|
8046
7860
|
captionIds: input.captions.map(() => this.idFactory("caption"))
|
|
8047
7861
|
};
|
|
8048
|
-
const state = this.dsl.getSnapshot();
|
|
8049
|
-
const plainSegments = input.script.segments === void 0 ? state["audio-script-entities"][result.audioScriptId]?.segments.map((segment) => segment.id) ?? [] : result.segmentIds;
|
|
8050
|
-
const plainPhoneticIds = input.script.phoneticScriptId === void 0 ? plainSegments.map((segmentId) => {
|
|
8051
|
-
const origins = Object.values(state["derived-from-relations"]).filter((origin) => {
|
|
8052
|
-
if (origin.entityId !== result.mediaId) return false;
|
|
8053
|
-
return matchesPlainPhoneticOrigin(state, origin.sourceId, result.audioScriptId, segmentId);
|
|
8054
|
-
});
|
|
8055
|
-
if (origins.length > 1) throw new Error("Ambiguous plain recording origin");
|
|
8056
|
-
return origins[0]?.sourceId ?? this.idFactory("phonetic-script");
|
|
8057
|
-
}) : [];
|
|
8058
7862
|
this.write("applyVoiceover", {
|
|
8059
7863
|
input,
|
|
8060
7864
|
result
|
|
8061
|
-
}, voiceoverPlan(
|
|
7865
|
+
}, voiceoverPlan(this.dsl.getSnapshot(), input, result), options);
|
|
8062
7866
|
return result;
|
|
8063
7867
|
}
|
|
8064
7868
|
voiceoverScriptId(script) {
|
|
@@ -8075,12 +7879,8 @@ var DslEditor = class {
|
|
|
8075
7879
|
* it; an unchanged one keeps its file. Recordings already spoken from it keep their derived-from.
|
|
8076
7880
|
*/
|
|
8077
7881
|
async setPhoneticScript(input, options) {
|
|
8078
|
-
this.dsl.assertWritable();
|
|
8079
7882
|
const id = input.phoneticScriptId ?? this.idFactory("phonetic-script");
|
|
8080
7883
|
const range = segmentRange(input);
|
|
8081
|
-
assertId(id);
|
|
8082
|
-
assertSegmentRange(range, requireSegment(this.dsl.getSnapshot(), range.audioScriptId, range.segmentId).text);
|
|
8083
|
-
assertPhonemeScript(input.phonemeScript);
|
|
8084
7884
|
const file = input.phonemeScript === void 0 ? {} : await this.fileFor(id, input.phonemeScript);
|
|
8085
7885
|
const exists = Object.hasOwn(this.dsl.getSnapshot()["phonetic-script-entities"], id);
|
|
8086
7886
|
const cleared = {
|
|
@@ -8289,13 +8089,14 @@ function toVideoDraft(snapshot) {
|
|
|
8289
8089
|
duration_ms: clip.durationMs
|
|
8290
8090
|
};
|
|
8291
8091
|
if (track.role === "video_clip") {
|
|
8292
|
-
|
|
8092
|
+
const placeholder = source?.system === "placeholder";
|
|
8093
|
+
if (!source || !["image", "video"].includes(source.kind) || !(source.system === "memota" || placeholder)) fail("Visual playback requires a memota Image or Video, or a placeholder Image");
|
|
8293
8094
|
if (clip.fillTimeline) fail("VideoDraft cannot express timeline-filling visual content");
|
|
8294
8095
|
parts[clip.clipId] = { video_clip: {
|
|
8295
8096
|
...common,
|
|
8296
8097
|
play_in: clip.inMs,
|
|
8297
8098
|
play_out: clip.outMs,
|
|
8298
|
-
origin_media_id: source.key,
|
|
8099
|
+
origin_media_id: placeholder && source.key === BLANK_PLACEHOLDER.key ? "" : source.key,
|
|
8299
8100
|
volume: clip.volume,
|
|
8300
8101
|
...snapshot["clip-entities"][clip.clipId].seqMarker.rate === void 0 ? {} : { speed_shift: {
|
|
8301
8102
|
category: "linear",
|
|
@@ -8304,8 +8105,7 @@ function toVideoDraft(snapshot) {
|
|
|
8304
8105
|
} }
|
|
8305
8106
|
} };
|
|
8306
8107
|
} else if (track.role === "speech") {
|
|
8307
|
-
if (!source || source.kind !== "
|
|
8308
|
-
if (source.system !== "memota" && !(source.kind === "voiceover-recording" && source.system === "memota-speech")) fail("Speech playback requires a Memota audio file");
|
|
8108
|
+
if (!source || source.kind !== "voiceover-recording" || source.system !== "memota-speech") fail("Legacy Speech requires a memota-speech VoiceoverRecording");
|
|
8309
8109
|
requireWholeAudio(source, clip, fail);
|
|
8310
8110
|
if (clip.fillTimeline) fail("Legacy Speech cannot express timeline fill");
|
|
8311
8111
|
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]] : []);
|
|
@@ -8322,7 +8122,7 @@ function toVideoDraft(snapshot) {
|
|
|
8322
8122
|
audio_storage_key: source.storageKey,
|
|
8323
8123
|
origin_speech_id: source.key,
|
|
8324
8124
|
volume: clip.volume,
|
|
8325
|
-
audio_script:
|
|
8125
|
+
audio_script: spokenText(snapshot, sourceId),
|
|
8326
8126
|
voice: voice ? {
|
|
8327
8127
|
id: voice.key,
|
|
8328
8128
|
name: voice.name
|
|
@@ -8418,15 +8218,6 @@ function spokenText(snapshot, recordingId) {
|
|
|
8418
8218
|
if (!first) return "";
|
|
8419
8219
|
if (first.phoneticScriptId !== void 0) return snapshot.phoneticText[first.phoneticScriptId]?.text ?? "";
|
|
8420
8220
|
const script = snapshot["audio-script-entities"][first.audioScriptId];
|
|
8421
|
-
if (first.phoneticScriptIds !== void 0 && script) {
|
|
8422
|
-
const ids = first.phoneticScriptIds;
|
|
8423
|
-
const selections = ids.map((id) => snapshot["phonetic-script-entities"][id]);
|
|
8424
|
-
if (!(ids.length === script.segments.length && script.segments.every((segment) => {
|
|
8425
|
-
const matching = ids.filter((id) => snapshot["phonetic-script-entities"][id]?.phoneme.segmentId === segment.id);
|
|
8426
|
-
const run = matching.length === 1 ? snapshot.phoneticText[matching[0]] : void 0;
|
|
8427
|
-
return run?.start === 0 && run.end === segment.text.length;
|
|
8428
|
-
})) || selections.some((selection) => selection.phoneme.phonemeScript !== void 0)) projectionError("ambiguous-script", `media-entities/${recordingId}`, "Multiple phonetic sources require complete plain selections of one script");
|
|
8429
|
-
}
|
|
8430
8221
|
return script ? script.segments.map((segment) => segment.text).join("\n") : "";
|
|
8431
8222
|
}
|
|
8432
8223
|
//#endregion
|
|
@@ -8517,6 +8308,7 @@ function prepare(input, options) {
|
|
|
8517
8308
|
if (facts.has(id)) fail("mediaFacts", `Duplicate facts for ${fact.system}/${fact.key}`);
|
|
8518
8309
|
facts.set(id, fact);
|
|
8519
8310
|
}
|
|
8311
|
+
for (const fact of facts.values()) if (fact.system === "placeholder" && facts.has(resourceId("media", "memota", fact.key))) fail("mediaFacts", `Media cannot be both present and missing: ${fact.key}`);
|
|
8520
8312
|
const register = (media) => {
|
|
8521
8313
|
const id = resourceId("media", media.system, media.key), old = imported.get(id);
|
|
8522
8314
|
if (old) {
|
|
@@ -8542,6 +8334,15 @@ function prepare(input, options) {
|
|
|
8542
8334
|
const mediaByPart = /* @__PURE__ */ new Map(), scriptByRecording = /* @__PURE__ */ new Map(), scriptsByPart = /* @__PURE__ */ new Map();
|
|
8543
8335
|
const voices = /* @__PURE__ */ new Set();
|
|
8544
8336
|
for (const [id, p] of Object.entries(library)) if (p.video_clip) {
|
|
8337
|
+
if (p.video_clip.origin_media_id === "") {
|
|
8338
|
+
mediaByPart.set(id, register(BLANK_PLACEHOLDER));
|
|
8339
|
+
continue;
|
|
8340
|
+
}
|
|
8341
|
+
const missing = p.video_clip.origin_media_id && facts.get(resourceId("media", "placeholder", p.video_clip.origin_media_id));
|
|
8342
|
+
if (missing) {
|
|
8343
|
+
mediaByPart.set(id, register(missing));
|
|
8344
|
+
continue;
|
|
8345
|
+
}
|
|
8545
8346
|
const fact = factFor("memota", p.video_clip.origin_media_id);
|
|
8546
8347
|
if (fact.kind !== "image" && fact.kind !== "video") fail(`part_library/${id}`, "Expected Image or Video facts");
|
|
8547
8348
|
mediaByPart.set(id, register(fact));
|
|
@@ -8765,5 +8566,122 @@ function importedScript(id, text, mintSegment) {
|
|
|
8765
8566
|
segments
|
|
8766
8567
|
};
|
|
8767
8568
|
}
|
|
8569
|
+
function normalizeLegacyVideoDraft(input, mediaFacts = []) {
|
|
8570
|
+
const draft = structuredClone(input);
|
|
8571
|
+
const diagnostics = [];
|
|
8572
|
+
const note = (code, path, message) => diagnostics.push({
|
|
8573
|
+
code,
|
|
8574
|
+
path,
|
|
8575
|
+
message
|
|
8576
|
+
});
|
|
8577
|
+
const library = draft.part_library ?? {};
|
|
8578
|
+
const tracks = [
|
|
8579
|
+
...draft.above_main_tracks ?? [],
|
|
8580
|
+
...draft.main_track ? [draft.main_track] : [],
|
|
8581
|
+
...draft.below_main_tracks ?? []
|
|
8582
|
+
];
|
|
8583
|
+
const facts = new Map(mediaFacts.map((fact) => [resourceId("media", fact.system, fact.key), fact]));
|
|
8584
|
+
const round = (owner, field, path) => {
|
|
8585
|
+
const fields = owner;
|
|
8586
|
+
const value = fields[field];
|
|
8587
|
+
if (typeof value !== "number" || !Number.isFinite(value) || Number.isSafeInteger(value)) return;
|
|
8588
|
+
const rounded = Math.round(value);
|
|
8589
|
+
fields[field] = rounded;
|
|
8590
|
+
note("legacy-time-rounded", `${path}/${field}`, `${value} → ${rounded}`);
|
|
8591
|
+
};
|
|
8592
|
+
for (const track of tracks) for (const item of track.items ?? []) round(item, "abs_time_position", `tracks/${track.id}/${item.part_id}`);
|
|
8593
|
+
for (const [id, part] of Object.entries(library)) {
|
|
8594
|
+
const path = `part_library/${id}`;
|
|
8595
|
+
if (part.video_clip) for (const f of [
|
|
8596
|
+
"play_in",
|
|
8597
|
+
"play_out",
|
|
8598
|
+
"duration_ms"
|
|
8599
|
+
]) round(part.video_clip, f, path);
|
|
8600
|
+
if (part.speech) for (const f of ["duration_ms", "media_duration_ms"]) round(part.speech, f, path);
|
|
8601
|
+
if (part.caption) for (const f of [
|
|
8602
|
+
"duration_ms",
|
|
8603
|
+
"initial_duration_ms",
|
|
8604
|
+
"start_ms"
|
|
8605
|
+
]) round(part.caption, f, path);
|
|
8606
|
+
if (part.bgm) round(part.bgm, "duration_ms", path);
|
|
8607
|
+
}
|
|
8608
|
+
for (const aggregation of draft.part_aggregations ?? []) for (const attachment of aggregation.attachments ?? []) round(attachment, "relative_time_position", `part_aggregations/${aggregation.body_part_id}/${attachment.part_id}`);
|
|
8609
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
8610
|
+
const drop = (id, why) => {
|
|
8611
|
+
dropped.add(id);
|
|
8612
|
+
note("legacy-part-dropped", `part_library/${id}`, why);
|
|
8613
|
+
};
|
|
8614
|
+
for (const [id, part] of Object.entries(library)) {
|
|
8615
|
+
const path = `part_library/${id}`;
|
|
8616
|
+
const clip = part.video_clip;
|
|
8617
|
+
const sound = part.bgm ?? part.speech;
|
|
8618
|
+
const fact = part.bgm ? facts.get(resourceId("media", "memota", part.bgm.origin_media_id ?? "")) : part.speech && facts.get(resourceId("media", "memota-speech", part.speech.origin_speech_id ?? ""));
|
|
8619
|
+
if (sound && fact && "storageKey" in fact && fact.storageKey && fact.storageKey !== sound.audio_storage_key) {
|
|
8620
|
+
note("legacy-storage-key-current", `${path}/audio_storage_key`, `${sound.audio_storage_key} → ${fact.storageKey}`);
|
|
8621
|
+
sound.audio_storage_key = fact.storageKey;
|
|
8622
|
+
}
|
|
8623
|
+
if (!clip) continue;
|
|
8624
|
+
const rate = clip.speed_shift?.config?.linear?.speed ?? 1;
|
|
8625
|
+
const inMs = clip.play_in ?? 0;
|
|
8626
|
+
const outMs = clip.play_out ?? (clip.duration_ms === void 0 ? void 0 : inMs + Math.round(clip.duration_ms * rate));
|
|
8627
|
+
const source = facts.get(resourceId("media", "memota", clip.origin_media_id ?? ""));
|
|
8628
|
+
let windowEnd = outMs;
|
|
8629
|
+
if (source?.kind === "video" && outMs !== void 0 && outMs > source.durationMs) {
|
|
8630
|
+
windowEnd = source.durationMs;
|
|
8631
|
+
if (windowEnd > inMs) {
|
|
8632
|
+
note("legacy-window-cut", `${path}/play_out`, `${outMs} → ${windowEnd} (source length)`);
|
|
8633
|
+
clip.play_out = windowEnd;
|
|
8634
|
+
if (clip.duration_ms !== void 0) clip.duration_ms = Math.max(0, Math.round((windowEnd - inMs) / rate));
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
if (windowEnd === void 0) continue;
|
|
8638
|
+
const length = windowEnd - inMs;
|
|
8639
|
+
if (length <= 0) drop(id, `Empty window ${inMs}–${windowEnd}`);
|
|
8640
|
+
else if (clip.origin_media_id === "" && length / rate < 40) drop(id, `Placeholder sliver of ${length / rate}ms`);
|
|
8641
|
+
}
|
|
8642
|
+
for (const id of dropped) delete library[id];
|
|
8643
|
+
for (const track of tracks) track.items = track.items?.filter((item) => !dropped.has(item.part_id ?? ""));
|
|
8644
|
+
const abs = /* @__PURE__ */ new Map();
|
|
8645
|
+
for (const track of tracks) for (const item of track.items ?? []) abs.set(item.part_id ?? "", item.abs_time_position ?? 0);
|
|
8646
|
+
const mainOrder = new Map((draft.main_track?.items ?? []).map((item, index) => [item.part_id ?? "", index]));
|
|
8647
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
8648
|
+
for (const aggregation of draft.part_aggregations ?? []) {
|
|
8649
|
+
const host = aggregation.body_part_id ?? "";
|
|
8650
|
+
for (const attachment of aggregation.attachments ?? []) {
|
|
8651
|
+
const speech = attachment.part_id ?? "";
|
|
8652
|
+
const path = `part_aggregations/${host}/${speech}`;
|
|
8653
|
+
if (!library[speech]?.speech) note("legacy-attachment-dropped", path, "Not a placed Speech");
|
|
8654
|
+
else if (!library[host]?.video_clip || !mainOrder.has(host)) note("legacy-attachment-dropped", path, "Host is not a placed visual clip");
|
|
8655
|
+
else candidates.set(speech, [...candidates.get(speech) ?? [], {
|
|
8656
|
+
host,
|
|
8657
|
+
offset: attachment.relative_time_position ?? 0
|
|
8658
|
+
}]);
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8661
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
8662
|
+
for (const [speech, list] of candidates) {
|
|
8663
|
+
if (list.length === 1) {
|
|
8664
|
+
chosen.set(speech, list[0]);
|
|
8665
|
+
continue;
|
|
8666
|
+
}
|
|
8667
|
+
const playing = list.filter((c) => (abs.get(c.host) ?? 0) + c.offset === abs.get(speech)).sort((a, b) => mainOrder.get(a.host) - mainOrder.get(b.host));
|
|
8668
|
+
const pick = playing[0] ?? list[0];
|
|
8669
|
+
chosen.set(speech, pick);
|
|
8670
|
+
note(playing.length ? "legacy-speech-host-chosen" : "legacy-speech-host-unmatched", `part_aggregations/${pick.host}/${speech}`, `${list.length} hosts: ${list.map((c) => c.host).join(", ")}`);
|
|
8671
|
+
}
|
|
8672
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
8673
|
+
for (const [speech, { host, offset }] of chosen) byHost.set(host, [...byHost.get(host) ?? [], {
|
|
8674
|
+
part_id: speech,
|
|
8675
|
+
relative_time_position: offset
|
|
8676
|
+
}]);
|
|
8677
|
+
if (draft.part_aggregations) draft.part_aggregations = [...new Set(draft.part_aggregations.map((a) => a.body_part_id ?? ""))].filter((host) => byHost.has(host)).map((host) => ({
|
|
8678
|
+
body_part_id: host,
|
|
8679
|
+
attachments: byHost.get(host)
|
|
8680
|
+
}));
|
|
8681
|
+
return {
|
|
8682
|
+
draft,
|
|
8683
|
+
diagnostics
|
|
8684
|
+
};
|
|
8685
|
+
}
|
|
8768
8686
|
//#endregion
|
|
8769
|
-
export { tupleHash as S,
|
|
8687
|
+
export { tupleHash as C, resourceId as S, DslProjectionError as _, createInitialMedeoDsl as a, defaultIdFactory as b, MedeoDsl as c, documentFormat as d, medeoDslLoroShape as f, resolveDslLayout as g, timeAnchorCodec as h, DEFAULT_UNIT_TIME_MS as i, UnsupportedDocumentFormatError as l, isInvalidAtomic as m, fromVideoDraft as n, DslEditor as o, captionContentCodec as p, toVideoDraft as r, toAudioScriptViews as s, normalizeLegacyVideoDraft as t, assertDslDocument as u, BLANK_PLACEHOLDER as v, relationId as x, missingMediaPlaceholder as y };
|