@mengine/medeo-client 2.0.1-alpha.25 → 2.0.1-alpha.27
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-Bx9xf40e.js → document-BGYPKtJ3.js} +41 -174
- package/dist/{index-BYv9FPSS.d.ts → index-CcIoMxs1.d.ts} +74 -339
- package/dist/index.d.ts +16 -82
- package/dist/index.js +31 -280
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as assertValidVideoDocument, A as decodeEntityRelationRows, B as updateEntityFields, C as applyFieldChanges, Ct as speedOf, D as EntityProjectionGraph, Dt as base64ToBytes, E as projectEntityTimeline, Et as videoDocumentMirrorSchema, F as assembleEntityContent, G as hasSequence, H as isJsonObject, I as assemblePhoneticScriptContent, J as buildSpeechHostMap, K as isMediaAssetVariantKind, L as assembleScriptText, M as relationsTo, N as ScriptCompositionError, O as resolveEntityTimelineLayout, Ot as bytesToBase64, P as assembleCaptionContent, Q as VideoDocumentValidationError, R as findComposedAudioScript, S as equalJson, St as effectiveVideoClipDurationMs, T as resolveFieldPath, Tt as recordEntries, U as createEntityId, V as variantBaseEntityIds, W as createRelationId, X as fromVideoDocument, Y as derivePositionFromAbs, Z as toVideoDocument, _ as audioScriptAssetOf, _t as TIMELINE_SKELETON_DURATION_MS, a as createMirrorVideoDocument, at as findLaneTrack, b as phoneticScriptAssetOf, bt as safeDurationMs, c as DocumentMutationGuard, ct as solveVideoDocument, d as LoroEntityDraft, dt as fillMainTrackTimeGaps, et as validateVideoDocument, f as readEntityDocument, ft as reassignSpeechesToVideoClipsByTime, g as audioScriptAssetHash, gt as syncAggregatedClipsTimePosition, h as audioScriptAssetFields, ht as resolveSpeechOverlapByShiftingVideos, i as MirrorVideoDocumentAdapter, it as ensureLaneTrack, j as relationsFrom, k as EntityTimelineProjectionError, l as LORO_ENTITY_SCHEMA, lt as cascadeAfterVideoClipChanges, m as audioScriptAssetContent, mt as resolveAllSpeechOverlaps, n as createPlainMemoryAdapter, nt as videoDocumentSchema, o as createMirrorVideoDocumentAdapter, ot as hasResolvedPlacement, p as entityOrderGroups, pt as recalculateTimelineDuration, q as buildInitialVideoDocument, r as generatePartId, rt as LANE_KINDS_IN_STACK_ORDER, s as readVideoDocumentFromDraft, st as laneTrackId, t as PlainMemoryAdapter, tt as partUnionSchema, u as LoroEntityDocument, ut as arrangeMainTrackSeamlessly, v as phoneticScriptAssetContent, vt as isEmptyVideoClip, w as assertFieldChanges, wt as partUnionToDraft, x as scriptSourceAssetOf, xt as DEFAULT_UNIT_TIME_MS, y as phoneticScriptAssetHash, yt as partDurationMs, z as selectAudioScriptSegment } from "./document-BGYPKtJ3.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
3
|
import { LoroDoc, UndoManager, VersionVector } from "loro-crdt";
|
|
4
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
|
-
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
6
4
|
import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
|
|
7
5
|
import { DisposableSet, EventBus, Task } from "@mengine/utils";
|
|
8
6
|
import { BaseDocStorage, DummyConnection } from "@mengine/storage";
|
|
@@ -216,36 +214,20 @@ function findSseFrameBoundary(buffer) {
|
|
|
216
214
|
//#endregion
|
|
217
215
|
//#region src/entity-crdt/compile-rows.ts
|
|
218
216
|
/** Lower a detached ordinary-object edit against its actual causal Loro baseline. */
|
|
219
|
-
function compileEntityRows(snapshot, rows, validate
|
|
217
|
+
function compileEntityRows(snapshot, rows, validate) {
|
|
220
218
|
const doc = new LoroDoc();
|
|
221
219
|
doc.import(snapshot);
|
|
222
|
-
return applyEntityRows(new LoroEntityDocument(doc, validate), rows
|
|
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
|
-
};
|
|
220
|
+
return applyEntityRows(new LoroEntityDocument(doc, validate), rows);
|
|
236
221
|
}
|
|
237
222
|
/** Compile an edit on an existing peer, retaining its native local undo history. */
|
|
238
|
-
function applyEntityRows(editor, rows
|
|
223
|
+
function applyEntityRows(editor, rows) {
|
|
239
224
|
const baseline = editor.snapshot();
|
|
240
225
|
const before = baseline.rows;
|
|
241
226
|
const oldEntities = new Map(before.entities.map((row) => [row.entityId, row]));
|
|
242
227
|
const newEntities = new Map(rows.entities.map((row) => [row.entityId, row]));
|
|
243
228
|
const oldRelations = new Map(before.relations.map((row) => [row.relationId, row]));
|
|
244
229
|
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]));
|
|
247
230
|
if (newEntities.size !== rows.entities.length || newRelations.size !== rows.relations.length) throw new Error("Duplicate domain identity");
|
|
248
|
-
validateEntityRelationTransition(before, rows);
|
|
249
231
|
return {
|
|
250
232
|
update: editor.transact((draft) => {
|
|
251
233
|
for (const row of before.relations) {
|
|
@@ -253,7 +235,7 @@ function applyEntityRows(editor, rows, options = {}) {
|
|
|
253
235
|
if (!next) draft.unlink(row.relationId);
|
|
254
236
|
else {
|
|
255
237
|
if (next.relationKind !== row.relationKind || next.endpoint0EntityId !== row.endpoint0EntityId || next.endpoint1EntityId !== row.endpoint1EntityId) throw new Error("Relation identity cannot change kind or endpoint positions");
|
|
256
|
-
if (!equalJson(row.trace, next.trace)) draft.
|
|
238
|
+
if (!equalJson(row.metadata, next.metadata) || !equalJson(row.trace, next.trace)) draft.patchRelation(row.relationId, next.metadata, next.trace);
|
|
257
239
|
}
|
|
258
240
|
}
|
|
259
241
|
let attached = baseline.project.audioScriptEntityId !== void 0;
|
|
@@ -271,15 +253,12 @@ function applyEntityRows(editor, rows, options = {}) {
|
|
|
271
253
|
}
|
|
272
254
|
}
|
|
273
255
|
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);
|
|
275
256
|
const beforeOrder = entityOrderGroups(before), afterOrder = entityOrderGroups(rows);
|
|
276
257
|
for (const group of new Set([...beforeOrder.keys(), ...afterOrder.keys()])) {
|
|
277
258
|
const next = afterOrder.get(group) ?? [];
|
|
278
259
|
if (!equalJson(beforeOrder.get(group) ?? [], next)) draft.order(group, next);
|
|
279
260
|
}
|
|
280
261
|
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);
|
|
283
262
|
}),
|
|
284
263
|
state: editor.snapshot()
|
|
285
264
|
};
|
|
@@ -453,16 +432,6 @@ function ensureEditorFoundation(initial, idFactory = (prefix) => `${prefix}_${gl
|
|
|
453
432
|
//#endregion
|
|
454
433
|
//#region src/entity-editor/entity-graph-http-client.ts
|
|
455
434
|
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
|
-
};
|
|
466
435
|
/** Browser-safe authenticated Loro client for a document's Entity graph. */
|
|
467
436
|
var EntityGraphHttpClient = class {
|
|
468
437
|
options;
|
|
@@ -479,8 +448,6 @@ var EntityGraphHttpClient = class {
|
|
|
479
448
|
}
|
|
480
449
|
/**
|
|
481
450
|
* 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.
|
|
484
451
|
*
|
|
485
452
|
* Deletion intent is derived from baseline IDs omitted from `nextRows`, so a
|
|
486
453
|
* transient create-then-remove never becomes a request to delete unknown
|
|
@@ -491,17 +458,13 @@ var EntityGraphHttpClient = class {
|
|
|
491
458
|
const next = cloneAndValidateRows(nextRows);
|
|
492
459
|
assertMediaAssetWritePolicy(baseState.rows, next);
|
|
493
460
|
const compiled = compileEntityRows(base64ToBytes(baseState.loroSnapshot), next);
|
|
494
|
-
return this.commitUpdate(bytesToBase64(compiled.update)
|
|
461
|
+
return this.commitUpdate(bytesToBase64(compiled.update));
|
|
495
462
|
}
|
|
496
|
-
/** Send prepared bytes
|
|
497
|
-
async commitUpdate(update
|
|
498
|
-
if (expectedRevision !== void 0 && !isNonNegativeSafeInteger(expectedRevision)) throw new Error("expectedRevision must be a nonnegative safe integer");
|
|
463
|
+
/** Send the same prepared bytes on retry; never recompile an unconfirmed edit. */
|
|
464
|
+
async commitUpdate(update) {
|
|
499
465
|
return this.request({
|
|
500
466
|
method: "POST",
|
|
501
|
-
body: JSON.stringify({
|
|
502
|
-
update,
|
|
503
|
-
...expectedRevision === void 0 ? {} : { expected_revision: expectedRevision }
|
|
504
|
-
})
|
|
467
|
+
body: JSON.stringify({ update })
|
|
505
468
|
});
|
|
506
469
|
}
|
|
507
470
|
async request(init) {
|
|
@@ -510,21 +473,10 @@ var EntityGraphHttpClient = class {
|
|
|
510
473
|
headers: this.transport.headers()
|
|
511
474
|
});
|
|
512
475
|
const payload = await safeReadJson(response);
|
|
513
|
-
if (!response.ok) throw
|
|
476
|
+
if (!response.ok) throw new MengineHttpRequestError(response.status, payload);
|
|
514
477
|
return parseWireState(payload, this.options.docId);
|
|
515
478
|
}
|
|
516
479
|
};
|
|
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
|
-
}
|
|
528
480
|
function parseWireState(value, expectedDocId) {
|
|
529
481
|
if (!isRecord(value) || typeof value.doc_id !== "string" || !isNonNegativeSafeInteger(value.revision)) throw new Error("Invalid entity-state response envelope");
|
|
530
482
|
if (value.doc_id !== expectedDocId) throw new Error(`Entity-state response doc_id mismatch: expected "${expectedDocId}"`);
|
|
@@ -1276,7 +1228,7 @@ var EntityTimelineEditor = class {
|
|
|
1276
1228
|
}
|
|
1277
1229
|
});
|
|
1278
1230
|
}
|
|
1279
|
-
this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, phonetic.entityId, input.captions);
|
|
1231
|
+
this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voiceover.entityId, phonetic.entityId, input.captions);
|
|
1280
1232
|
this.resolveOneVoiceoverOverlap(draft);
|
|
1281
1233
|
return {
|
|
1282
1234
|
voiceoverClipEntityId,
|
|
@@ -1463,7 +1415,7 @@ var EntityTimelineEditor = class {
|
|
|
1463
1415
|
requireComposedAudioScript(draft, phonetic.entityId);
|
|
1464
1416
|
return phonetic;
|
|
1465
1417
|
}
|
|
1466
|
-
/** Preserve the factual synthesis input
|
|
1418
|
+
/** Preserve the factual synthesis input, regardless of persisted endpoint positions. */
|
|
1467
1419
|
ensurePhoneticRender(draft, voiceoverAudioEntityId, phoneticScriptEntityId) {
|
|
1468
1420
|
const renders = draft.relations.filter((relation) => relation.relationKind === "derived-from" && (relation.endpoint0EntityId === voiceoverAudioEntityId || relation.endpoint1EntityId === voiceoverAudioEntityId));
|
|
1469
1421
|
if (renders.length > 1) throw new Error(`Voiceover "${voiceoverAudioEntityId}" has multiple PhoneticScript render relations`);
|
|
@@ -1473,7 +1425,7 @@ var EntityTimelineEditor = class {
|
|
|
1473
1425
|
}
|
|
1474
1426
|
draft.relations.push(this.createRelation(draft, "derived-from", voiceoverAudioEntityId, phoneticScriptEntityId));
|
|
1475
1427
|
}
|
|
1476
|
-
reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, phoneticScriptEntityId, captions) {
|
|
1428
|
+
reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voiceoverAudioEntityId, phoneticScriptEntityId, captions) {
|
|
1477
1429
|
const audioScriptEntityId = requireComposedAudioScript(draft, phoneticScriptEntityId);
|
|
1478
1430
|
const requested = new Set(captions.map((caption) => caption.captionClipEntityId));
|
|
1479
1431
|
if (requested.size !== captions.length) throw new Error("Caption Clip ids must be unique within a voiceover take");
|
|
@@ -1498,6 +1450,10 @@ var EntityTimelineEditor = class {
|
|
|
1498
1450
|
};
|
|
1499
1451
|
draft.entities.push(content);
|
|
1500
1452
|
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
|
+
} }));
|
|
1501
1457
|
this.insertPlacedClipIntoDraft(draft, {
|
|
1502
1458
|
clipEntityId: caption.captionClipEntityId,
|
|
1503
1459
|
trackEntityId: captionTrack.entityId,
|
|
@@ -1544,6 +1500,7 @@ var EntityTimelineEditor = class {
|
|
|
1544
1500
|
...rate === 1 ? {} : { rate }
|
|
1545
1501
|
}
|
|
1546
1502
|
});
|
|
1503
|
+
this.replaceCaptionAlignment(draft, content.entityId, voiceoverAudioEntityId, startMs, durationMs);
|
|
1547
1504
|
}
|
|
1548
1505
|
}
|
|
1549
1506
|
ensureRoleTrackForClip(draft, clipEntityId, role) {
|
|
@@ -1552,6 +1509,14 @@ var EntityTimelineEditor = class {
|
|
|
1552
1509
|
const timeline = requireEntityKind(draft, otherEndpoint(requireOneContain(draft, track.entityId, "contained"), track.entityId), "timeline");
|
|
1553
1510
|
return this.ensureRoleTrack(draft, timeline.entityId, role);
|
|
1554
1511
|
}
|
|
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
|
+
}
|
|
1555
1520
|
/**
|
|
1556
1521
|
* Remove one placed Caption use. The composed AudioScript outlives the
|
|
1557
1522
|
* caption: it may anchor further captions and remains the text owner.
|
|
@@ -1947,224 +1912,6 @@ function cloneRows(rows) {
|
|
|
1947
1912
|
};
|
|
1948
1913
|
}
|
|
1949
1914
|
//#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
|
|
2168
1915
|
//#region src/entity-editor/legacy-entity-migration.ts
|
|
2169
1916
|
/** One-time import only. Ordinary editor operations must never reverse-diff a legacy document. */
|
|
2170
1917
|
function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
@@ -2329,6 +2076,10 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
|
|
|
2329
2076
|
selection,
|
|
2330
2077
|
...caption.style === void 0 ? {} : { style: toEntityCaptionStyle(caption.style) }
|
|
2331
2078
|
});
|
|
2079
|
+
link("caption-alignment", content, voiceover, { alignment: {
|
|
2080
|
+
start: caption.start_ms,
|
|
2081
|
+
end: caption.start_ms + duration
|
|
2082
|
+
} });
|
|
2332
2083
|
markerPayload = {
|
|
2333
2084
|
inMs: 0,
|
|
2334
2085
|
outMs: duration
|
|
@@ -5247,4 +4998,4 @@ function serverCovers(serverVV, target) {
|
|
|
5247
4998
|
return covers(server, target);
|
|
5248
4999
|
}
|
|
5249
5000
|
//#endregion
|
|
5250
|
-
export { DEFAULT_UNIT_TIME_MS, EntityGraphHttpClient,
|
|
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 };
|
package/dist/testing.d.ts
CHANGED
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dt as base64ToBytes, Ot as bytesToBase64, o as createMirrorVideoDocumentAdapter } from "./document-BGYPKtJ3.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.27",
|
|
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/sync": "2.0.1-alpha.
|
|
33
|
-
"@mengine/
|
|
31
|
+
"@mengine/utils": "2.0.1-alpha.27",
|
|
32
|
+
"@mengine/sync": "2.0.1-alpha.27",
|
|
33
|
+
"@mengine/storage": "2.0.1-alpha.27"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^25.9.1",
|