@mengine/medeo-client 2.0.1-alpha.26 → 2.0.1-alpha.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-Ct11SMBS.js → document-DcpvSvQK.js} +185 -29
- package/dist/{index-CcIoMxs1.d.ts → index-BYv9FPSS.d.ts} +339 -74
- package/dist/index.d.ts +86 -16
- package/dist/index.js +422 -32
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as fromVideoDocument, A as resolveEntityTimelineLayout, At as base64ToBytes, B as assembleScriptText, C as canonicalJson, Ct as safeDurationMs, D as resolveFieldPath, Dt as partUnionToDraft, E as assertFieldChanges, Et as speedOf, F as relationsTo, G as isJsonObject, H as selectAudioScriptSegment, I as ScriptCompositionError, J as hasSequence, K as createEntityId, L as assembleCaptionContent, M as validateEntityRelationTransition, N as decodeEntityRelationRows, O as projectEntityTimeline, Ot as recordEntries, P as relationsFrom, Q as derivePositionFromAbs, R as assembleEntityContent, S as scriptSourceAssetOf, St as partDurationMs, T as applyFieldChanges, Tt as effectiveVideoClipDurationMs, U as updateEntityFields, V as findComposedAudioScript, W as variantBaseEntityIds, X as buildInitialVideoDocument, Y as isMediaAssetVariantKind, Z as buildSpeechHostMap, _ as audioScriptAssetHash, _t as resolveAllSpeechOverlaps, a as createMirrorVideoDocument, at as videoDocumentSchema, b as phoneticScriptAssetHash, bt as TIMELINE_SKELETON_DURATION_MS, c as DocumentMutationGuard, ct as findLaneTrack, d as LoroEntityDraft, dt as solveVideoDocument, et as toVideoDocument, f as readEntityDocument, ft as cascadeAfterVideoClipChanges, g as audioScriptAssetFields, gt as recalculateTimelineDuration, h as audioScriptAssetContent, ht as reassignSpeechesToVideoClipsByTime, i as MirrorVideoDocumentAdapter, it as partUnionSchema, j as EntityTimelineProjectionError, jt as bytesToBase64, k as EntityProjectionGraph, kt as videoDocumentMirrorSchema, l as LORO_ENTITY_SCHEMA, lt as hasResolvedPlacement, m as entityOrderGroups, mt as fillMainTrackTimeGaps, n as createPlainMemoryAdapter, nt as assertValidVideoDocument, o as createMirrorVideoDocumentAdapter, ot as LANE_KINDS_IN_STACK_ORDER, p as readEntityRelationRecords, pt as arrangeMainTrackSeamlessly, q as createRelationId, r as generatePartId, rt as validateVideoDocument, s as readVideoDocumentFromDraft, st as ensureLaneTrack, t as PlainMemoryAdapter, tt as VideoDocumentValidationError, u as LoroEntityDocument, ut as laneTrackId, v as audioScriptAssetOf, vt as resolveSpeechOverlapByShiftingVideos, w as equalJson, wt as DEFAULT_UNIT_TIME_MS, x as phoneticScriptAssetOf, xt as isEmptyVideoClip, y as phoneticScriptAssetContent, yt as syncAggregatedClipsTimePosition, z as assemblePhoneticScriptContent } from "./document-DcpvSvQK.js";
|
|
2
2
|
import { addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, t as schemas_exports } from "./schemas.js";
|
|
3
|
-
import { LoroDoc, UndoManager, VersionVector } from "loro-crdt";
|
|
3
|
+
import { LoroDoc, LoroMap, UndoManager, VersionVector } from "loro-crdt";
|
|
4
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
4
6
|
import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
|
|
5
7
|
import { DisposableSet, EventBus, Task } from "@mengine/utils";
|
|
6
8
|
import { BaseDocStorage, DummyConnection } from "@mengine/storage";
|
|
@@ -214,20 +216,36 @@ function findSseFrameBoundary(buffer) {
|
|
|
214
216
|
//#endregion
|
|
215
217
|
//#region src/entity-crdt/compile-rows.ts
|
|
216
218
|
/** Lower a detached ordinary-object edit against its actual causal Loro baseline. */
|
|
217
|
-
function compileEntityRows(snapshot, rows, validate) {
|
|
219
|
+
function compileEntityRows(snapshot, rows, validate, options = {}) {
|
|
218
220
|
const doc = new LoroDoc();
|
|
219
221
|
doc.import(snapshot);
|
|
220
|
-
return applyEntityRows(new LoroEntityDocument(doc, validate), rows);
|
|
222
|
+
return applyEntityRows(new LoroEntityDocument(doc, validate), rows, options);
|
|
223
|
+
}
|
|
224
|
+
/** Publish final rows as imported causal operations, outside the caller's local undo history. */
|
|
225
|
+
function publishEntityRows(editor, rows, options = {}) {
|
|
226
|
+
const from = editor.doc.oplogVersion();
|
|
227
|
+
const compiled = compileEntityRows(editor.doc.export({ mode: "snapshot" }), rows, void 0, options);
|
|
228
|
+
editor.import(compiled.update);
|
|
229
|
+
return {
|
|
230
|
+
update: editor.doc.export({
|
|
231
|
+
mode: "update",
|
|
232
|
+
from
|
|
233
|
+
}),
|
|
234
|
+
state: editor.snapshot()
|
|
235
|
+
};
|
|
221
236
|
}
|
|
222
237
|
/** Compile an edit on an existing peer, retaining its native local undo history. */
|
|
223
|
-
function applyEntityRows(editor, rows) {
|
|
238
|
+
function applyEntityRows(editor, rows, options = {}) {
|
|
224
239
|
const baseline = editor.snapshot();
|
|
225
240
|
const before = baseline.rows;
|
|
226
241
|
const oldEntities = new Map(before.entities.map((row) => [row.entityId, row]));
|
|
227
242
|
const newEntities = new Map(rows.entities.map((row) => [row.entityId, row]));
|
|
228
243
|
const oldRelations = new Map(before.relations.map((row) => [row.relationId, row]));
|
|
229
244
|
const newRelations = new Map(rows.relations.map((row) => [row.relationId, row]));
|
|
245
|
+
const oldClipContent = new Map(before.relations.filter((row) => row.relationKind === "contain" && oldEntities.get(row.endpoint0EntityId)?.entityKind === "clip").map((row) => [row.endpoint0EntityId, row.relationId]));
|
|
246
|
+
const newClipContent = new Map(rows.relations.filter((row) => row.relationKind === "contain" && newEntities.get(row.endpoint0EntityId)?.entityKind === "clip").map((row) => [row.endpoint0EntityId, row.relationId]));
|
|
230
247
|
if (newEntities.size !== rows.entities.length || newRelations.size !== rows.relations.length) throw new Error("Duplicate domain identity");
|
|
248
|
+
validateEntityRelationTransition(before, rows);
|
|
231
249
|
return {
|
|
232
250
|
update: editor.transact((draft) => {
|
|
233
251
|
for (const row of before.relations) {
|
|
@@ -235,7 +253,7 @@ function applyEntityRows(editor, rows) {
|
|
|
235
253
|
if (!next) draft.unlink(row.relationId);
|
|
236
254
|
else {
|
|
237
255
|
if (next.relationKind !== row.relationKind || next.endpoint0EntityId !== row.endpoint0EntityId || next.endpoint1EntityId !== row.endpoint1EntityId) throw new Error("Relation identity cannot change kind or endpoint positions");
|
|
238
|
-
if (!equalJson(row.
|
|
256
|
+
if (!equalJson(row.trace, next.trace)) draft.patchRelationTrace(row.relationId, next.trace);
|
|
239
257
|
}
|
|
240
258
|
}
|
|
241
259
|
let attached = baseline.project.audioScriptEntityId !== void 0;
|
|
@@ -253,12 +271,15 @@ function applyEntityRows(editor, rows) {
|
|
|
253
271
|
}
|
|
254
272
|
}
|
|
255
273
|
for (const row of rows.relations) if (!oldRelations.has(row.relationId)) draft.link(row);
|
|
274
|
+
for (const [clipId, relationId] of newClipContent) if (oldClipContent.get(clipId) !== relationId) draft.selectClipContent(relationId);
|
|
256
275
|
const beforeOrder = entityOrderGroups(before), afterOrder = entityOrderGroups(rows);
|
|
257
276
|
for (const group of new Set([...beforeOrder.keys(), ...afterOrder.keys()])) {
|
|
258
277
|
const next = afterOrder.get(group) ?? [];
|
|
259
278
|
if (!equalJson(beforeOrder.get(group) ?? [], next)) draft.order(group, next);
|
|
260
279
|
}
|
|
261
280
|
for (const row of before.entities) if (!newEntities.has(row.entityId)) draft.delete(row.entityId);
|
|
281
|
+
for (const [slot, entityId] of Object.entries(options.projectAttachments ?? {})) draft.attach(slot, entityId);
|
|
282
|
+
for (const [clipEntityId, phoneticScriptEntityId] of Object.entries(options.phoneticScriptSelections ?? {})) draft.selectPhoneticScript(clipEntityId, phoneticScriptEntityId);
|
|
262
283
|
}),
|
|
263
284
|
state: editor.snapshot()
|
|
264
285
|
};
|
|
@@ -432,6 +453,16 @@ function ensureEditorFoundation(initial, idFactory = (prefix) => `${prefix}_${gl
|
|
|
432
453
|
//#endregion
|
|
433
454
|
//#region src/entity-editor/entity-graph-http-client.ts
|
|
434
455
|
const MENGINE_API_PREFIX = "/api/mengine/v1";
|
|
456
|
+
var EntityGraphRevisionConflictError = class extends MengineHttpRequestError {
|
|
457
|
+
expectedRevision;
|
|
458
|
+
currentRevision;
|
|
459
|
+
constructor(expectedRevision, currentRevision, payload) {
|
|
460
|
+
super(409, payload);
|
|
461
|
+
this.expectedRevision = expectedRevision;
|
|
462
|
+
this.currentRevision = currentRevision;
|
|
463
|
+
this.name = "EntityGraphRevisionConflictError";
|
|
464
|
+
}
|
|
465
|
+
};
|
|
435
466
|
/** Browser-safe authenticated Loro client for a document's Entity graph. */
|
|
436
467
|
var EntityGraphHttpClient = class {
|
|
437
468
|
options;
|
|
@@ -448,6 +479,8 @@ var EntityGraphHttpClient = class {
|
|
|
448
479
|
}
|
|
449
480
|
/**
|
|
450
481
|
* Compile edits against the original causal snapshot and publish native Loro operations.
|
|
482
|
+
* The base revision is sent as an optimistic precondition, so a stale adoption
|
|
483
|
+
* fails loudly instead of letting Loro choose between concurrent selections.
|
|
451
484
|
*
|
|
452
485
|
* Deletion intent is derived from baseline IDs omitted from `nextRows`, so a
|
|
453
486
|
* transient create-then-remove never becomes a request to delete unknown
|
|
@@ -458,13 +491,17 @@ var EntityGraphHttpClient = class {
|
|
|
458
491
|
const next = cloneAndValidateRows(nextRows);
|
|
459
492
|
assertMediaAssetWritePolicy(baseState.rows, next);
|
|
460
493
|
const compiled = compileEntityRows(base64ToBytes(baseState.loroSnapshot), next);
|
|
461
|
-
return this.commitUpdate(bytesToBase64(compiled.update));
|
|
494
|
+
return this.commitUpdate(bytesToBase64(compiled.update), baseState.revision);
|
|
462
495
|
}
|
|
463
|
-
/** Send
|
|
464
|
-
async commitUpdate(update) {
|
|
496
|
+
/** Send prepared bytes directly; omit `expectedRevision` for immutable fact publication. */
|
|
497
|
+
async commitUpdate(update, expectedRevision) {
|
|
498
|
+
if (expectedRevision !== void 0 && !isNonNegativeSafeInteger(expectedRevision)) throw new Error("expectedRevision must be a nonnegative safe integer");
|
|
465
499
|
return this.request({
|
|
466
500
|
method: "POST",
|
|
467
|
-
body: JSON.stringify({
|
|
501
|
+
body: JSON.stringify({
|
|
502
|
+
update,
|
|
503
|
+
...expectedRevision === void 0 ? {} : { expected_revision: expectedRevision }
|
|
504
|
+
})
|
|
468
505
|
});
|
|
469
506
|
}
|
|
470
507
|
async request(init) {
|
|
@@ -473,10 +510,21 @@ var EntityGraphHttpClient = class {
|
|
|
473
510
|
headers: this.transport.headers()
|
|
474
511
|
});
|
|
475
512
|
const payload = await safeReadJson(response);
|
|
476
|
-
if (!response.ok) throw new MengineHttpRequestError(response.status, payload);
|
|
513
|
+
if (!response.ok) throw parseRevisionConflict(response.status, payload) ?? new MengineHttpRequestError(response.status, payload);
|
|
477
514
|
return parseWireState(payload, this.options.docId);
|
|
478
515
|
}
|
|
479
516
|
};
|
|
517
|
+
function parseRevisionConflict(status, value) {
|
|
518
|
+
if (status !== 409 || !isRecord(value) || !isRecord(value.error) || value.error.code !== "revision_conflict") return;
|
|
519
|
+
const error = value.error;
|
|
520
|
+
if (typeof error.message !== "string" || !isNonNegativeSafeInteger(error.expected_revision) || !isNonNegativeSafeInteger(error.current_revision)) return;
|
|
521
|
+
return new EntityGraphRevisionConflictError(error.expected_revision, error.current_revision, { error: {
|
|
522
|
+
code: "revision_conflict",
|
|
523
|
+
message: error.message,
|
|
524
|
+
expected_revision: error.expected_revision,
|
|
525
|
+
current_revision: error.current_revision
|
|
526
|
+
} });
|
|
527
|
+
}
|
|
480
528
|
function parseWireState(value, expectedDocId) {
|
|
481
529
|
if (!isRecord(value) || typeof value.doc_id !== "string" || !isNonNegativeSafeInteger(value.revision)) throw new Error("Invalid entity-state response envelope");
|
|
482
530
|
if (value.doc_id !== expectedDocId) throw new Error(`Entity-state response doc_id mismatch: expected "${expectedDocId}"`);
|
|
@@ -1228,7 +1276,7 @@ var EntityTimelineEditor = class {
|
|
|
1228
1276
|
}
|
|
1229
1277
|
});
|
|
1230
1278
|
}
|
|
1231
|
-
this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId,
|
|
1279
|
+
this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, phonetic.entityId, input.captions);
|
|
1232
1280
|
this.resolveOneVoiceoverOverlap(draft);
|
|
1233
1281
|
return {
|
|
1234
1282
|
voiceoverClipEntityId,
|
|
@@ -1415,7 +1463,7 @@ var EntityTimelineEditor = class {
|
|
|
1415
1463
|
requireComposedAudioScript(draft, phonetic.entityId);
|
|
1416
1464
|
return phonetic;
|
|
1417
1465
|
}
|
|
1418
|
-
/** Preserve the factual synthesis input
|
|
1466
|
+
/** Preserve the factual synthesis input as `[rendered Audio, PhoneticScript]`. */
|
|
1419
1467
|
ensurePhoneticRender(draft, voiceoverAudioEntityId, phoneticScriptEntityId) {
|
|
1420
1468
|
const renders = draft.relations.filter((relation) => relation.relationKind === "derived-from" && (relation.endpoint0EntityId === voiceoverAudioEntityId || relation.endpoint1EntityId === voiceoverAudioEntityId));
|
|
1421
1469
|
if (renders.length > 1) throw new Error(`Voiceover "${voiceoverAudioEntityId}" has multiple PhoneticScript render relations`);
|
|
@@ -1425,7 +1473,7 @@ var EntityTimelineEditor = class {
|
|
|
1425
1473
|
}
|
|
1426
1474
|
draft.relations.push(this.createRelation(draft, "derived-from", voiceoverAudioEntityId, phoneticScriptEntityId));
|
|
1427
1475
|
}
|
|
1428
|
-
reconcileVoiceoverCaptions(draft, voiceoverClipEntityId,
|
|
1476
|
+
reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, phoneticScriptEntityId, captions) {
|
|
1429
1477
|
const audioScriptEntityId = requireComposedAudioScript(draft, phoneticScriptEntityId);
|
|
1430
1478
|
const requested = new Set(captions.map((caption) => caption.captionClipEntityId));
|
|
1431
1479
|
if (requested.size !== captions.length) throw new Error("Caption Clip ids must be unique within a voiceover take");
|
|
@@ -1450,10 +1498,6 @@ var EntityTimelineEditor = class {
|
|
|
1450
1498
|
};
|
|
1451
1499
|
draft.entities.push(content);
|
|
1452
1500
|
assertCaptionScript(draft, content.entityId, audioScriptEntityId);
|
|
1453
|
-
draft.relations.push(this.createRelation(draft, "caption-alignment", content.entityId, voiceoverAudioEntityId, { alignment: {
|
|
1454
|
-
start: startMs,
|
|
1455
|
-
end: startMs + durationMs
|
|
1456
|
-
} }));
|
|
1457
1501
|
this.insertPlacedClipIntoDraft(draft, {
|
|
1458
1502
|
clipEntityId: caption.captionClipEntityId,
|
|
1459
1503
|
trackEntityId: captionTrack.entityId,
|
|
@@ -1500,7 +1544,6 @@ var EntityTimelineEditor = class {
|
|
|
1500
1544
|
...rate === 1 ? {} : { rate }
|
|
1501
1545
|
}
|
|
1502
1546
|
});
|
|
1503
|
-
this.replaceCaptionAlignment(draft, content.entityId, voiceoverAudioEntityId, startMs, durationMs);
|
|
1504
1547
|
}
|
|
1505
1548
|
}
|
|
1506
1549
|
ensureRoleTrackForClip(draft, clipEntityId, role) {
|
|
@@ -1509,14 +1552,6 @@ var EntityTimelineEditor = class {
|
|
|
1509
1552
|
const timeline = requireEntityKind(draft, otherEndpoint(requireOneContain(draft, track.entityId, "contained"), track.entityId), "timeline");
|
|
1510
1553
|
return this.ensureRoleTrack(draft, timeline.entityId, role);
|
|
1511
1554
|
}
|
|
1512
|
-
replaceCaptionAlignment(draft, captionEntityId, voiceoverAudioEntityId, startMs, durationMs) {
|
|
1513
|
-
const removed = new Set(incidentRelations(draft, captionEntityId).filter((relation) => relation.relationKind === "caption-alignment").map((relation) => relation.relationId));
|
|
1514
|
-
draft.relations = draft.relations.filter((relation) => !removed.has(relation.relationId));
|
|
1515
|
-
draft.relations.push(this.createRelation(draft, "caption-alignment", captionEntityId, voiceoverAudioEntityId, { alignment: {
|
|
1516
|
-
start: startMs,
|
|
1517
|
-
end: startMs + durationMs
|
|
1518
|
-
} }));
|
|
1519
|
-
}
|
|
1520
1555
|
/**
|
|
1521
1556
|
* Remove one placed Caption use. The composed AudioScript outlives the
|
|
1522
1557
|
* caption: it may anchor further captions and remains the text owner.
|
|
@@ -1912,6 +1947,224 @@ function cloneRows(rows) {
|
|
|
1912
1947
|
};
|
|
1913
1948
|
}
|
|
1914
1949
|
//#endregion
|
|
1950
|
+
//#region src/entity-editor/revise-audio-script.ts
|
|
1951
|
+
/** Revise text as an independent authored document, never as a version-family mutation. */
|
|
1952
|
+
function reviseAudioScript(rows, input) {
|
|
1953
|
+
if (typeof input.editId !== "string" || !input.editId.trim()) throw new Error("editId is required");
|
|
1954
|
+
if (!Array.isArray(input.edits) || !input.edits.length) throw new Error("At least one segment edit is required");
|
|
1955
|
+
const edits = /* @__PURE__ */ new Map();
|
|
1956
|
+
for (const edit of input.edits) {
|
|
1957
|
+
if (!edit.segmentId?.trim() || typeof edit.text !== "string" || !edit.text.trim()) throw new Error("Segment text must be nonempty");
|
|
1958
|
+
if (edits.has(edit.segmentId)) throw new Error("Duplicate segment edit");
|
|
1959
|
+
edits.set(edit.segmentId, edit.text);
|
|
1960
|
+
}
|
|
1961
|
+
const signature = hash({
|
|
1962
|
+
...input,
|
|
1963
|
+
captionUsages: input.captionUsages ?? []
|
|
1964
|
+
});
|
|
1965
|
+
const prior = rows.relations.filter((r) => r.relationKind === "derived-from" && r.trace.command === "revise-audio-script" && r.trace.editId === input.editId);
|
|
1966
|
+
if (prior.length && (prior.length !== 1 || prior[0].trace.signature !== signature)) throw new Error("AudioScript edit identity conflict");
|
|
1967
|
+
const source = rows.entities.find((row) => row.entityId === input.sourceAudioScriptEntityId);
|
|
1968
|
+
if (source?.entityKind !== "audio-script") throw new Error("AudioScript source is missing");
|
|
1969
|
+
assembleScriptText({
|
|
1970
|
+
...source,
|
|
1971
|
+
entityKind: "audio-script"
|
|
1972
|
+
});
|
|
1973
|
+
const payload = structuredClone(source.payload);
|
|
1974
|
+
if (!Array.isArray(payload.segments)) throw new Error("AudioScript must own its segments");
|
|
1975
|
+
const segmentIds = new Set(payload.segments.filter(isJsonObject).map((segment) => segment.segmentId));
|
|
1976
|
+
for (const segmentId of edits.keys()) if (!segmentIds.has(segmentId)) throw new Error(`Unknown segment ${segmentId}`);
|
|
1977
|
+
const changedSegments = /* @__PURE__ */ new Set();
|
|
1978
|
+
payload.segments = payload.segments.map((segment) => {
|
|
1979
|
+
if (segment === null || typeof segment !== "object" || Array.isArray(segment)) throw new Error("Invalid segment");
|
|
1980
|
+
const text = edits.get(String(segment.segmentId)) ?? segment.text;
|
|
1981
|
+
if (text !== segment.text) changedSegments.add(String(segment.segmentId));
|
|
1982
|
+
return {
|
|
1983
|
+
...segment,
|
|
1984
|
+
text
|
|
1985
|
+
};
|
|
1986
|
+
});
|
|
1987
|
+
if (!changedSegments.size) {
|
|
1988
|
+
if (prior.length) throw new Error("AudioScript edit identity conflict");
|
|
1989
|
+
return {
|
|
1990
|
+
rows,
|
|
1991
|
+
audioScriptEntityId: source.entityId,
|
|
1992
|
+
captionUsages: [],
|
|
1993
|
+
changed: false
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
delete payload.assetContentHash;
|
|
1997
|
+
const id = hash(input.editId);
|
|
1998
|
+
const audioScriptEntityId = createEntityId(`entity_script_revision_${id}`);
|
|
1999
|
+
const audioScript = {
|
|
2000
|
+
entityId: audioScriptEntityId,
|
|
2001
|
+
entityKind: "audio-script",
|
|
2002
|
+
payload
|
|
2003
|
+
};
|
|
2004
|
+
const provenance = {
|
|
2005
|
+
relationId: createRelationId(`relation_script_revision_${id}`),
|
|
2006
|
+
relationKind: "derived-from",
|
|
2007
|
+
endpoint0EntityId: audioScriptEntityId,
|
|
2008
|
+
endpoint1EntityId: source.entityId,
|
|
2009
|
+
metadata: {},
|
|
2010
|
+
trace: {
|
|
2011
|
+
command: "revise-audio-script",
|
|
2012
|
+
editId: input.editId,
|
|
2013
|
+
signature
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
const selectedMarkers = /* @__PURE__ */ new Set();
|
|
2017
|
+
const planCaption = (usage, index, targetAudioScriptEntityId) => {
|
|
2018
|
+
if (selectedMarkers.has(usage.markerEntityId)) throw new Error("Duplicate caption usage");
|
|
2019
|
+
selectedMarkers.add(usage.markerEntityId);
|
|
2020
|
+
const caption = rows.entities.find((row) => row.entityId === usage.captionEntityId);
|
|
2021
|
+
if (caption?.entityKind !== "caption") throw new Error("Caption is missing");
|
|
2022
|
+
const assembled = assembleCaptionContent(rows, caption.entityId);
|
|
2023
|
+
if (assembled.audioScript.entityId !== source.entityId) throw new Error("Caption does not use the source AudioScript");
|
|
2024
|
+
const selection = assembled.caption.payload.selection;
|
|
2025
|
+
if (isJsonObject(selection) && selection.textRange !== void 0 && typeof selection.segmentId === "string" && changedSegments.has(selection.segmentId) && usage.selection === void 0) throw new Error("Changed partial Caption requires an explicit selection");
|
|
2026
|
+
const bases = caption.payload.baseEntityIds;
|
|
2027
|
+
if (!Array.isArray(bases) || !bases.includes(source.entityId)) throw new Error("Caption requires a direct AudioScript base");
|
|
2028
|
+
const clipMarkers = rows.relations.filter((r) => r.relationKind === "marker" && r.endpoint0EntityId === usage.clipEntityId);
|
|
2029
|
+
const clipContents = rows.relations.filter((r) => r.relationKind === "contain" && r.endpoint0EntityId === usage.clipEntityId);
|
|
2030
|
+
const markerOwners = rows.relations.filter((r) => r.relationKind === "marker" && r.endpoint1EntityId === usage.markerEntityId);
|
|
2031
|
+
if (clipMarkers.length !== 1 || clipMarkers[0].endpoint1EntityId !== usage.markerEntityId || markerOwners.length !== 1 || clipContents.length !== 1) throw new Error("Caption usage conflict");
|
|
2032
|
+
const newCaptionId = createEntityId(`entity_caption_revision_${id}_${index}`);
|
|
2033
|
+
return {
|
|
2034
|
+
clone: {
|
|
2035
|
+
...caption,
|
|
2036
|
+
entityId: newCaptionId,
|
|
2037
|
+
payload: {
|
|
2038
|
+
...structuredClone(caption.payload),
|
|
2039
|
+
baseEntityIds: bases.map((base) => base === source.entityId ? targetAudioScriptEntityId : base),
|
|
2040
|
+
...usage.selection === void 0 ? {} : { selection: structuredClone(usage.selection) }
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
clipContent: clipContents[0],
|
|
2044
|
+
newCaptionId,
|
|
2045
|
+
relationId: createRelationId(`relation_caption_revision_${id}_${index}`),
|
|
2046
|
+
trace: {
|
|
2047
|
+
command: "revise-audio-script",
|
|
2048
|
+
editId: input.editId,
|
|
2049
|
+
usageIndex: index
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
};
|
|
2053
|
+
if (prior.length) {
|
|
2054
|
+
const currentAudioScriptEntityId = prior[0].endpoint0EntityId;
|
|
2055
|
+
if (!equalJson(prior[0], {
|
|
2056
|
+
...provenance,
|
|
2057
|
+
relationId: prior[0].relationId,
|
|
2058
|
+
endpoint0EntityId: currentAudioScriptEntityId
|
|
2059
|
+
})) throw new Error("AudioScript edit identity conflict");
|
|
2060
|
+
const preservedScripts = rows.entities.filter((row) => row.entityId === currentAudioScriptEntityId);
|
|
2061
|
+
const preservedScript = preservedScripts[0]?.entityKind === "audio-script" ? {
|
|
2062
|
+
...preservedScripts[0],
|
|
2063
|
+
payload: Object.fromEntries(Object.entries(preservedScripts[0].payload).filter(([key]) => key !== "assetContentHash"))
|
|
2064
|
+
} : preservedScripts[0];
|
|
2065
|
+
if (preservedScripts.length !== 1 || !equalJson(preservedScript, {
|
|
2066
|
+
...audioScript,
|
|
2067
|
+
entityId: currentAudioScriptEntityId
|
|
2068
|
+
})) throw new Error("AudioScript edit identity conflict");
|
|
2069
|
+
let relations = [...rows.relations];
|
|
2070
|
+
let changed = false;
|
|
2071
|
+
const captionUsages = [];
|
|
2072
|
+
for (const [index, usage] of (input.captionUsages ?? []).entries()) {
|
|
2073
|
+
const plan = planCaption(usage, index, currentAudioScriptEntityId);
|
|
2074
|
+
const preservedCaptions = rows.entities.filter((row) => row.entityId === plan.newCaptionId);
|
|
2075
|
+
if (preservedCaptions.length !== 1 || !equalJson(preservedCaptions[0], plan.clone)) throw new Error("Caption usage conflict after revision");
|
|
2076
|
+
const plannedEdge = {
|
|
2077
|
+
relationId: plan.relationId,
|
|
2078
|
+
relationKind: "contain",
|
|
2079
|
+
endpoint0EntityId: plan.clipContent.endpoint0EntityId,
|
|
2080
|
+
endpoint1EntityId: plan.newCaptionId,
|
|
2081
|
+
metadata: structuredClone(plan.clipContent.metadata),
|
|
2082
|
+
trace: plan.trace
|
|
2083
|
+
};
|
|
2084
|
+
if (plan.clipContent.relationId === plan.relationId) {
|
|
2085
|
+
if (!equalJson(plan.clipContent, plannedEdge)) throw new Error("Caption usage conflict after revision");
|
|
2086
|
+
} else {
|
|
2087
|
+
if (plan.clipContent.endpoint1EntityId !== usage.captionEntityId) throw new Error("Caption usage conflict after revision");
|
|
2088
|
+
relations = relations.filter((row) => row.relationId !== plan.clipContent.relationId);
|
|
2089
|
+
relations.push(plannedEdge);
|
|
2090
|
+
changed = true;
|
|
2091
|
+
}
|
|
2092
|
+
captionUsages.push({
|
|
2093
|
+
clipEntityId: usage.clipEntityId,
|
|
2094
|
+
markerEntityId: usage.markerEntityId,
|
|
2095
|
+
previousCaptionEntityId: usage.captionEntityId,
|
|
2096
|
+
captionEntityId: plan.newCaptionId
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
if (!changed) return {
|
|
2100
|
+
rows,
|
|
2101
|
+
audioScriptEntityId: currentAudioScriptEntityId,
|
|
2102
|
+
captionUsages,
|
|
2103
|
+
changed: false
|
|
2104
|
+
};
|
|
2105
|
+
const revised = {
|
|
2106
|
+
entities: rows.entities,
|
|
2107
|
+
relations
|
|
2108
|
+
};
|
|
2109
|
+
decodeAndValidate(rows, revised);
|
|
2110
|
+
return {
|
|
2111
|
+
rows: revised,
|
|
2112
|
+
audioScriptEntityId: currentAudioScriptEntityId,
|
|
2113
|
+
captionUsages,
|
|
2114
|
+
changed: true
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
const result = {
|
|
2118
|
+
entities: [...rows.entities, audioScript],
|
|
2119
|
+
relations: [...rows.relations, provenance]
|
|
2120
|
+
};
|
|
2121
|
+
const entities = [...result.entities];
|
|
2122
|
+
let relations = [...result.relations];
|
|
2123
|
+
const captionUsages = [];
|
|
2124
|
+
for (const [index, usage] of (input.captionUsages ?? []).entries()) {
|
|
2125
|
+
const plan = planCaption(usage, index, audioScriptEntityId);
|
|
2126
|
+
if (plan.clipContent.endpoint1EntityId !== usage.captionEntityId) throw new Error("Caption usage conflict");
|
|
2127
|
+
entities.push(plan.clone);
|
|
2128
|
+
assembleCaptionContent({
|
|
2129
|
+
entities,
|
|
2130
|
+
relations
|
|
2131
|
+
}, plan.newCaptionId);
|
|
2132
|
+
relations = relations.filter((r) => r.relationId !== plan.clipContent.relationId);
|
|
2133
|
+
relations.push({
|
|
2134
|
+
relationId: plan.relationId,
|
|
2135
|
+
relationKind: "contain",
|
|
2136
|
+
endpoint0EntityId: plan.clipContent.endpoint0EntityId,
|
|
2137
|
+
endpoint1EntityId: plan.newCaptionId,
|
|
2138
|
+
metadata: structuredClone(plan.clipContent.metadata),
|
|
2139
|
+
trace: plan.trace
|
|
2140
|
+
});
|
|
2141
|
+
captionUsages.push({
|
|
2142
|
+
clipEntityId: usage.clipEntityId,
|
|
2143
|
+
markerEntityId: usage.markerEntityId,
|
|
2144
|
+
previousCaptionEntityId: usage.captionEntityId,
|
|
2145
|
+
captionEntityId: plan.newCaptionId
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
const revised = {
|
|
2149
|
+
entities,
|
|
2150
|
+
relations
|
|
2151
|
+
};
|
|
2152
|
+
decodeAndValidate(rows, revised);
|
|
2153
|
+
return {
|
|
2154
|
+
rows: revised,
|
|
2155
|
+
audioScriptEntityId,
|
|
2156
|
+
captionUsages,
|
|
2157
|
+
changed: true
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function decodeAndValidate(previous, revised) {
|
|
2161
|
+
decodeEntityRelationRows(revised, {});
|
|
2162
|
+
validateEntityRelationTransition(previous, revised);
|
|
2163
|
+
}
|
|
2164
|
+
function hash(value) {
|
|
2165
|
+
return bytesToHex(sha256(new TextEncoder().encode(canonicalJson(value))));
|
|
2166
|
+
}
|
|
2167
|
+
//#endregion
|
|
1915
2168
|
//#region src/entity-editor/legacy-entity-migration.ts
|
|
1916
2169
|
/** One-time import only. Ordinary editor operations must never reverse-diff a legacy document. */
|
|
1917
2170
|
function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
@@ -2076,10 +2329,6 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
|
2076
2329
|
selection,
|
|
2077
2330
|
...caption.style === void 0 ? {} : { style: toEntityCaptionStyle(caption.style) }
|
|
2078
2331
|
});
|
|
2079
|
-
link("caption-alignment", content, voiceover, { alignment: {
|
|
2080
|
-
start: caption.start_ms,
|
|
2081
|
-
end: caption.start_ms + duration
|
|
2082
|
-
} });
|
|
2083
2332
|
markerPayload = {
|
|
2084
2333
|
inMs: 0,
|
|
2085
2334
|
outMs: duration
|
|
@@ -2587,6 +2836,147 @@ function selectCaptionText(script, text) {
|
|
|
2587
2836
|
return matches[0];
|
|
2588
2837
|
}
|
|
2589
2838
|
//#endregion
|
|
2839
|
+
//#region src/entity-crdt/legacy-schema-migration.ts
|
|
2840
|
+
const LEGACY_KINDS = new Set([
|
|
2841
|
+
"timeline-track",
|
|
2842
|
+
"track-clip",
|
|
2843
|
+
"clip-marker",
|
|
2844
|
+
"marker-content",
|
|
2845
|
+
"audio-script-marker",
|
|
2846
|
+
"audio-script-source",
|
|
2847
|
+
"generated"
|
|
2848
|
+
]);
|
|
2849
|
+
function whole(value, label) {
|
|
2850
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Legacy SequenceMarker ${label} is invalid`);
|
|
2851
|
+
return value;
|
|
2852
|
+
}
|
|
2853
|
+
function legacyMarkerPayload(payload, annotation) {
|
|
2854
|
+
if (payload.inMs !== void 0 || payload.outMs !== void 0) return void 0;
|
|
2855
|
+
const keys = canonicalJson(Object.keys(payload).sort());
|
|
2856
|
+
if (!(annotation ? [[
|
|
2857
|
+
"duration",
|
|
2858
|
+
"segmentRanges",
|
|
2859
|
+
"sourceRange"
|
|
2860
|
+
]] : [
|
|
2861
|
+
["duration", "sourceRange"],
|
|
2862
|
+
[
|
|
2863
|
+
"anchorOffset",
|
|
2864
|
+
"duration",
|
|
2865
|
+
"sourceRange"
|
|
2866
|
+
],
|
|
2867
|
+
[
|
|
2868
|
+
"duration",
|
|
2869
|
+
"sourceRange",
|
|
2870
|
+
"targetRange"
|
|
2871
|
+
],
|
|
2872
|
+
[
|
|
2873
|
+
"duration",
|
|
2874
|
+
"sourceRange",
|
|
2875
|
+
"timeRemapping"
|
|
2876
|
+
]
|
|
2877
|
+
]).some((shape) => keys === canonicalJson(shape))) throw new Error("Legacy SequenceMarker has unsupported fields");
|
|
2878
|
+
if (!isJsonObject(payload.sourceRange) || !isJsonObject(payload.duration)) throw new Error("Legacy SequenceMarker ranges are invalid");
|
|
2879
|
+
const inMs = whole(payload.sourceRange.start, "sourceRange.start");
|
|
2880
|
+
const outMs = whole(payload.sourceRange.end, "sourceRange.end");
|
|
2881
|
+
const duration = whole(payload.duration.value, "duration.value");
|
|
2882
|
+
if (payload.duration.mode !== "fixed" || duration === 0 || outMs <= inMs) throw new Error("Legacy SequenceMarker duration is invalid");
|
|
2883
|
+
const rate = (outMs - inMs) / duration;
|
|
2884
|
+
if (!Number.isFinite(rate) || rate <= 0) throw new Error("Legacy SequenceMarker rate is invalid");
|
|
2885
|
+
if (annotation) {
|
|
2886
|
+
if (rate !== 1 || !Array.isArray(payload.segmentRanges)) throw new Error("Legacy AudioScript annotation Marker is invalid");
|
|
2887
|
+
return {
|
|
2888
|
+
inMs,
|
|
2889
|
+
outMs,
|
|
2890
|
+
segmentRanges: payload.segmentRanges
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
let startMs = 0;
|
|
2894
|
+
if (payload.anchorOffset !== void 0) startMs = whole(payload.anchorOffset, "anchorOffset");
|
|
2895
|
+
if (payload.targetRange !== void 0) {
|
|
2896
|
+
if (!isJsonObject(payload.targetRange)) throw new Error("Legacy SequenceMarker targetRange is invalid");
|
|
2897
|
+
startMs = whole(payload.targetRange.start, "targetRange.start");
|
|
2898
|
+
const targetEnd = whole(payload.targetRange.end, "targetRange.end");
|
|
2899
|
+
if (targetEnd <= startMs || targetEnd - startMs !== duration) throw new Error("Legacy SequenceMarker targetRange disagrees with duration");
|
|
2900
|
+
}
|
|
2901
|
+
if (payload.timeRemapping !== void 0) {
|
|
2902
|
+
const remapping = payload.timeRemapping;
|
|
2903
|
+
if (!isJsonObject(remapping) || canonicalJson(Object.keys(remapping).sort()) !== canonicalJson([
|
|
2904
|
+
"kind",
|
|
2905
|
+
"mode",
|
|
2906
|
+
"rate"
|
|
2907
|
+
]) || remapping.kind !== "linear" || remapping.mode !== "constant" || typeof remapping.rate !== "number" || !Number.isFinite(remapping.rate) || remapping.rate <= 0) throw new Error("Legacy SequenceMarker timeRemapping is invalid");
|
|
2908
|
+
if (remapping.rate !== rate) throw new Error("Legacy SequenceMarker timeRemapping disagrees with duration");
|
|
2909
|
+
}
|
|
2910
|
+
return {
|
|
2911
|
+
inMs,
|
|
2912
|
+
outMs,
|
|
2913
|
+
startMs,
|
|
2914
|
+
...rate === 1 ? {} : { rate }
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function relationId(root, kind) {
|
|
2918
|
+
return createRelationId(`relation_${bytesToHex(sha256(new TextEncoder().encode(canonicalJson([root, kind]))))}`);
|
|
2919
|
+
}
|
|
2920
|
+
function entityRoot(doc, id) {
|
|
2921
|
+
const origins = doc.getMap("medeo").get("origins");
|
|
2922
|
+
if (!(origins instanceof LoroMap)) throw new Error("Missing Loro entity origins");
|
|
2923
|
+
const origin = origins.get(id);
|
|
2924
|
+
return typeof origin === "string" ? origin : id;
|
|
2925
|
+
}
|
|
2926
|
+
function mappedRow(doc, row, records) {
|
|
2927
|
+
const endpoint0 = entityRoot(doc, row.endpoint0EntityId);
|
|
2928
|
+
const endpoint1 = entityRoot(doc, row.endpoint1EntityId);
|
|
2929
|
+
let kind;
|
|
2930
|
+
let endpoints;
|
|
2931
|
+
if (row.relationKind === "timeline-track" || row.relationKind === "track-clip") {
|
|
2932
|
+
kind = "contain";
|
|
2933
|
+
endpoints = [endpoint0, endpoint1];
|
|
2934
|
+
} else if (row.relationKind === "clip-marker" || row.relationKind === "audio-script-marker") {
|
|
2935
|
+
kind = "marker";
|
|
2936
|
+
endpoints = [endpoint0, endpoint1];
|
|
2937
|
+
} else if (row.relationKind === "audio-script-source" || row.relationKind === "generated") {
|
|
2938
|
+
kind = "derived-from";
|
|
2939
|
+
endpoints = [endpoint0, endpoint1];
|
|
2940
|
+
} else {
|
|
2941
|
+
const owners = records.filter((candidate) => !candidate.deleted && candidate.relationKind === "clip-marker" && entityRoot(doc, candidate.endpoint1EntityId) === endpoint0);
|
|
2942
|
+
if (owners.length !== 1) throw new Error(`Legacy marker-content ${row.relationId} requires exactly one clip-marker owner`);
|
|
2943
|
+
endpoints = [entityRoot(doc, owners[0].endpoint0EntityId), endpoint1];
|
|
2944
|
+
kind = "contain";
|
|
2945
|
+
}
|
|
2946
|
+
return {
|
|
2947
|
+
relationId: relationId(row.relationId, kind),
|
|
2948
|
+
relationKind: kind,
|
|
2949
|
+
endpoint0EntityId: createEntityId(endpoints[0]),
|
|
2950
|
+
endpoint1EntityId: createEntityId(endpoints[1]),
|
|
2951
|
+
metadata: row.metadata,
|
|
2952
|
+
trace: row.trace
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
/** Replace only the four positionally equivalent alpha23 relation kinds. */
|
|
2956
|
+
function migrateAlpha23EntityDocument(doc) {
|
|
2957
|
+
if (doc.getMap("medeo").get("schema") !== "medeo.entities.loro.v1") return void 0;
|
|
2958
|
+
const records = readEntityRelationRecords(doc);
|
|
2959
|
+
const legacy = records.filter((row) => !row.deleted && LEGACY_KINDS.has(row.relationKind));
|
|
2960
|
+
if (legacy.length === 0) return void 0;
|
|
2961
|
+
const replacements = legacy.map((row) => mappedRow(doc, row, records));
|
|
2962
|
+
const current = readEntityDocument(doc, true).rows;
|
|
2963
|
+
const markerPayloads = /* @__PURE__ */ new Map();
|
|
2964
|
+
for (const relation of current.relations) {
|
|
2965
|
+
if (relation.relationKind !== "clip-marker" && relation.relationKind !== "audio-script-marker") continue;
|
|
2966
|
+
const marker = current.entities.find((row) => row.entityId === relation.endpoint1EntityId);
|
|
2967
|
+
if (marker?.entityKind !== "sequence-marker") throw new Error(`Legacy ${relation.relationKind} ${relation.relationId} has no SequenceMarker`);
|
|
2968
|
+
const payload = legacyMarkerPayload(marker.payload, relation.relationKind === "audio-script-marker");
|
|
2969
|
+
if (payload !== void 0) markerPayloads.set(marker.entityId, payload);
|
|
2970
|
+
}
|
|
2971
|
+
const occupied = new Set(records.map((row) => String(row.relationId)));
|
|
2972
|
+
for (const row of replacements) if (occupied.has(row.relationId)) throw new Error(`Legacy relation migration ID collision: ${row.relationId}`);
|
|
2973
|
+
return new LoroEntityDocument(doc).transact((draft) => {
|
|
2974
|
+
for (const [markerId, payload] of markerPayloads) draft.replaceOwned(markerId, payload);
|
|
2975
|
+
for (const row of legacy) draft.unlink(row.relationId);
|
|
2976
|
+
for (const row of replacements) draft.link(row);
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
//#endregion
|
|
2590
2980
|
//#region src/editor/snapshot-utils.ts
|
|
2591
2981
|
function isMap(value) {
|
|
2592
2982
|
return value instanceof Map;
|
|
@@ -4998,4 +5388,4 @@ function serverCovers(serverVV, target) {
|
|
|
4998
5388
|
return covers(server, target);
|
|
4999
5389
|
}
|
|
5000
5390
|
//#endregion
|
|
5001
|
-
export { DEFAULT_UNIT_TIME_MS, EntityGraphHttpClient, EntityTimelineEditor, EntityTimelineProjectionError, IMPLEMENTED_SEMANTIC_OP_KINDS, LANE_KINDS_IN_STACK_ORDER, LORO_ENTITY_SCHEMA, LoroEntityDocument, LoroEntityDraft, ManualSyncDoc, MedeoHttpDocStorage, MemoryDocStorage, MengineAckFailedError, MengineDocSession, MengineHttpClient, MengineHttpRequestError, MenginePushRejectedError, MirrorVideoDocumentAdapter, PlainMemoryAdapter, SchemaValidator, ScriptCompositionError, SemanticEditor, TIMELINE_SKELETON_DURATION_MS, ValidationError, VideoDocumentValidationError, applyEntityRows, applyFieldChanges, arrangeMainTrackSeamlessly, assembleCaptionContent, assembleEntityContent, assemblePhoneticScriptContent, assembleScriptText, assertCanonicalEditorResources, assertEntityProjectionPreservesLegacy, assertFieldChanges, assertMediaAssetWritePolicy, assertValidVideoDocument, audioScriptAssetContent, audioScriptAssetFields, audioScriptAssetHash, audioScriptAssetOf, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, compileEntityRows, createEditSandbox, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, createPlainMemoryAdapter, decodeDocVersionMark, derivePositionFromAbs, effectiveVideoClipDurationMs, encodeDocVersionMark, ensureEditorFoundation, ensureLaneTrack, fillMainTrackTimeGaps, findComposedAudioScript, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, importMediaAsset, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, migrateLegacyTimelineToEntities, partDurationMs, partUnionSchema, phoneticScriptAssetContent, phoneticScriptAssetHash, phoneticScriptAssetOf, prepareCaptionContent, projectEntityTimeline, readDocumentAudioScript, readEntityDocument, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, replayJournal, resolveAllSpeechOverlaps, resolveEntityTimelineLayout, resolveFieldPath, resolveSpeechOverlapByShiftingVideos, safeDurationMs, schemas_exports as schemas, scriptSourceAssetOf, selectAudioScriptSegment, snapshotToPlain, solveVideoDocument, speedOf, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
|
|
5391
|
+
export { DEFAULT_UNIT_TIME_MS, EntityGraphHttpClient, EntityGraphRevisionConflictError, EntityTimelineEditor, EntityTimelineProjectionError, IMPLEMENTED_SEMANTIC_OP_KINDS, LANE_KINDS_IN_STACK_ORDER, LORO_ENTITY_SCHEMA, LoroEntityDocument, LoroEntityDraft, ManualSyncDoc, MedeoHttpDocStorage, MemoryDocStorage, MengineAckFailedError, MengineDocSession, MengineHttpClient, MengineHttpRequestError, MenginePushRejectedError, MirrorVideoDocumentAdapter, PlainMemoryAdapter, SchemaValidator, ScriptCompositionError, SemanticEditor, TIMELINE_SKELETON_DURATION_MS, ValidationError, VideoDocumentValidationError, applyEntityRows, applyFieldChanges, arrangeMainTrackSeamlessly, assembleCaptionContent, assembleEntityContent, assemblePhoneticScriptContent, assembleScriptText, assertCanonicalEditorResources, assertEntityProjectionPreservesLegacy, assertFieldChanges, assertMediaAssetWritePolicy, assertValidVideoDocument, audioScriptAssetContent, audioScriptAssetFields, audioScriptAssetHash, audioScriptAssetOf, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, compileEntityRows, createEditSandbox, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, createPlainMemoryAdapter, decodeDocVersionMark, derivePositionFromAbs, effectiveVideoClipDurationMs, encodeDocVersionMark, ensureEditorFoundation, ensureLaneTrack, fillMainTrackTimeGaps, findComposedAudioScript, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, importMediaAsset, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, migrateAlpha23EntityDocument, migrateLegacyTimelineToEntities, partDurationMs, partUnionSchema, phoneticScriptAssetContent, phoneticScriptAssetHash, phoneticScriptAssetOf, prepareCaptionContent, projectEntityTimeline, publishEntityRows, readDocumentAudioScript, readEntityDocument, readEntityRelationRecords, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, replayJournal, resolveAllSpeechOverlaps, resolveEntityTimelineLayout, resolveFieldPath, resolveSpeechOverlapByShiftingVideos, reviseAudioScript, safeDurationMs, schemas_exports as schemas, scriptSourceAssetOf, selectAudioScriptSegment, snapshotToPlain, solveVideoDocument, speedOf, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema };
|
package/dist/testing.d.ts
CHANGED
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { At as base64ToBytes, jt as bytesToBase64, o as createMirrorVideoDocumentAdapter } from "./document-DcpvSvQK.js";
|
|
2
2
|
import { t as classifyUpdate } from "./loro-relay-doc-cJSY-uau.js";
|
|
3
3
|
import { LoroDoc, VersionVector, encodeFrontiers } from "loro-crdt";
|
|
4
4
|
//#region src/testing/in-memory-mengine-server.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mengine/medeo-client",
|
|
3
|
-
"version": "2.0.1-alpha.
|
|
3
|
+
"version": "2.0.1-alpha.28",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"immer": "^10.2.0",
|
|
29
29
|
"loro-mirror": "^2.3.3",
|
|
30
30
|
"zod": "^4.4.3",
|
|
31
|
-
"@mengine/
|
|
32
|
-
"@mengine/utils": "2.0.1-alpha.
|
|
33
|
-
"@mengine/
|
|
31
|
+
"@mengine/sync": "2.0.1-alpha.28",
|
|
32
|
+
"@mengine/utils": "2.0.1-alpha.28",
|
|
33
|
+
"@mengine/storage": "2.0.1-alpha.28"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^25.9.1",
|