@mengine/medeo-client 1.3.1-alpha.0 → 1.3.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as videoDocumentMirrorSchema, A as ensureLaneTrack, B as resolveSpeechOverlapByShiftingVideos, C as toVideoDocument, D as partUnionSchema, E as validateVideoDocument, F as arrangeMainTrackSeamlessly, G as safeDurationMs, H as TIMELINE_SKELETON_DURATION_MS, I as fillMainTrackTimeGaps, J as VIDEO_DOCUMENT_SCHEMA_VERSION, K as DEFAULT_UNIT_TIME_MS, L as reassignSpeechesToVideoClipsByTime, M as laneTrackId, N as solveVideoDocument, O as videoDocumentSchema, P as cascadeAfterVideoClipChanges, Q as recordEntries, R as recalculateTimelineDuration, S as fromVideoDocument, T as assertValidVideoDocument, U as isEmptyVideoClip, V as syncAggregatedClipsTimePosition, W as partDurationMs, X as speedOf, Y as effectiveVideoClipDurationMs, Z as partUnionToDraft, _ as hasSequence, a as applyEntityTimelineProjection, b as buildSpeechHostMap, c as writeVideoDocumentToDraft, d as resolveEntityTimelineLayout, et as base64ToBytes, f as EntityTimelineProjectionError, g as createRelationId, h as createEntityId, i as MirrorVideoDocumentAdapter, j as findLaneTrack, k as LANE_KINDS_IN_STACK_ORDER, l as readVideoDocumentFromDraft, m as isJsonObject, n as createPlainMemoryAdapter, o as createMirrorVideoDocument, p as decodeEntityRelationRows, q as ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, r as generatePartId, s as createMirrorVideoDocumentAdapter, t as PlainMemoryAdapter, tt as bytesToBase64, u as projectEntityTimeline, v as isMediaAssetVariantKind, w as VideoDocumentValidationError, x as derivePositionFromAbs, y as buildInitialVideoDocument, z as resolveAllSpeechOverlaps } from "./document-Caidonf8.js";
1
+ import { $ as reassignSpeechesToVideoClipsByTime, A as isJsonObject, B as VideoDocumentValidationError, C as assembleEntityContent, D as selectAudioScriptSegments, E as findComposedAudioScript, F as buildInitialVideoDocument, G as LANE_KINDS_IN_STACK_ORDER, H as validateVideoDocument, I as buildSpeechHostMap, J as laneTrackId, K as ensureLaneTrack, L as derivePositionFromAbs, M as createRelationId, N as hasSequence, O as updateEntityFields, P as isMediaAssetVariantKind, Q as fillMainTrackTimeGaps, R as fromVideoDocument, S as assembleCaptionContent, T as assembleScriptText, U as partUnionSchema, V as assertValidVideoDocument, W as videoDocumentSchema, X as cascadeAfterVideoClipChanges, Y as solveVideoDocument, Z as arrangeMainTrackSeamlessly, _ as EntityProjectionGraph, _t as bytesToBase64, a as applyEntityTimelineProjection, at as isEmptyVideoClip, b as decodeEntityRelationRows, c as writeVideoDocumentToDraft, ct as DEFAULT_UNIT_TIME_MS, d as LoroEntityDocument, dt as effectiveVideoClipDurationMs, et as recalculateTimelineDuration, f as LoroEntityDraft, ft as speedOf, g as projectEntityTimeline, gt as base64ToBytes, h as equalJson, ht as videoDocumentMirrorSchema, i as MirrorVideoDocumentAdapter, it as TIMELINE_SKELETON_DURATION_MS, j as createEntityId, k as variantBaseEntityIds, l as readVideoDocumentFromDraft, lt as ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, m as entityOrderGroups, mt as recordEntries, n as createPlainMemoryAdapter, nt as resolveSpeechOverlapByShiftingVideos, o as createMirrorVideoDocument, ot as partDurationMs, p as readEntityDocument, pt as partUnionToDraft, q as findLaneTrack, r as generatePartId, rt as syncAggregatedClipsTimePosition, s as createMirrorVideoDocumentAdapter, st as safeDurationMs, t as PlainMemoryAdapter, tt as resolveAllSpeechOverlaps, u as LORO_ENTITY_SCHEMA, ut as VIDEO_DOCUMENT_SCHEMA_VERSION, v as resolveEntityTimelineLayout, w as assemblePhoneticScriptContent, x as ScriptCompositionError, y as EntityTimelineProjectionError, z as toVideoDocument } from "./document-BxWxPFTj.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, VersionVector } from "loro-crdt";
4
4
  import { ClientServerSynchronizer, DocManager } from "@mengine/sync";
@@ -217,6 +217,67 @@ function findSseFrameBoundary(buffer) {
217
217
  return Math.min(lf, crlf);
218
218
  }
219
219
  //#endregion
220
+ //#region src/entity-crdt/compile-rows.ts
221
+ /** Lower a detached ordinary-object edit against its actual causal Loro baseline. */
222
+ function compileEntityRows(snapshot, rows, validate) {
223
+ const doc = new LoroDoc();
224
+ doc.import(snapshot);
225
+ return applyEntityRows(new LoroEntityDocument(doc, validate), rows);
226
+ }
227
+ /** Compile an edit on an existing peer, retaining its native local undo history. */
228
+ function applyEntityRows(editor, rows) {
229
+ const before = editor.snapshot().rows;
230
+ const oldEntities = new Map(before.entities.map((row) => [row.entityId, row]));
231
+ const newEntities = new Map(rows.entities.map((row) => [row.entityId, row]));
232
+ const oldRelations = new Map(before.relations.map((row) => [row.relationId, row]));
233
+ const newRelations = new Map(rows.relations.map((row) => [row.relationId, row]));
234
+ if (newEntities.size !== rows.entities.length || newRelations.size !== rows.relations.length) throw new Error("Duplicate domain identity");
235
+ return {
236
+ update: editor.transact((draft) => {
237
+ for (const row of before.relations) {
238
+ const next = newRelations.get(row.relationId);
239
+ if (!next) draft.unlink(row.relationId);
240
+ else {
241
+ if (next.relationKind !== row.relationKind || next.endpoint0EntityId !== row.endpoint0EntityId || next.endpoint1EntityId !== row.endpoint1EntityId) throw new Error("Relation identity cannot change kind or endpoint positions");
242
+ if (!equalJson(row.metadata, next.metadata) || !equalJson(row.trace, next.trace)) draft.patchRelation(row.relationId, next.metadata, next.trace);
243
+ }
244
+ }
245
+ for (const row of rows.entities) {
246
+ const previous = oldEntities.get(row.entityId);
247
+ if (!previous) draft.create(row);
248
+ else {
249
+ if (previous.entityKind !== row.entityKind) throw new Error("Entity kind cannot change");
250
+ if (!equalJson(previous.payload, row.payload)) draft.replaceOwned(row.entityId, row.payload);
251
+ }
252
+ }
253
+ for (const row of rows.relations) if (!oldRelations.has(row.relationId)) draft.link(row);
254
+ const beforeOrder = entityOrderGroups(before), afterOrder = entityOrderGroups(rows);
255
+ for (const group of new Set([...beforeOrder.keys(), ...afterOrder.keys()])) {
256
+ const next = afterOrder.get(group) ?? [];
257
+ if (!equalJson(beforeOrder.get(group) ?? [], next)) draft.order(group, next);
258
+ }
259
+ for (const row of before.entities) if (!newEntities.has(row.entityId)) draft.delete(row.entityId);
260
+ }),
261
+ state: editor.snapshot()
262
+ };
263
+ }
264
+ //#endregion
265
+ //#region src/entity-editor/document-audio-script.ts
266
+ /** Read only the attached version and preserve every segment; never aggregate other scripts. */
267
+ function readDocumentAudioScript(state) {
268
+ const id = state.audioScriptEntityId;
269
+ if (id === null) throw new EntityTimelineProjectionError("Document AudioScript is not initialized");
270
+ if (typeof id !== "string" || id.trim() !== id || !id) throw new EntityTimelineProjectionError("Invalid document AudioScript identity");
271
+ const entity = state.rows.entities.find((row) => row.entityId === id);
272
+ if (!entity || entity.entityKind !== "audio-script") throw new EntityTimelineProjectionError(`Document AudioScript ${id} must name the project AudioScript`);
273
+ const content = assembleEntityContent(state.rows, createEntityId(id));
274
+ assembleScriptText(content);
275
+ return {
276
+ entityId: id,
277
+ segments: structuredClone(content.payload.segments)
278
+ };
279
+ }
280
+ //#endregion
220
281
  //#region src/entity-editor/editor-resources.ts
221
282
  const roleOrder$1 = {
222
283
  caption: 0,
@@ -231,8 +292,7 @@ const roleOrder$1 = {
231
292
  * Image/Video/Audio/Voice Entity must carry its own external locator, every
232
293
  * external asset id has exactly one claimant, and no media variant may be
233
294
  * bound through a legacy `physical-asset` Relation. Dual-entity media graphs
234
- * are rejected, not migrated. Caption inline text keeps its inline Asset and
235
- * `physical-asset` binding.
295
+ * are rejected, not migrated. Caption composes AudioScript text and has no physical text Asset.
236
296
  */
237
297
  function assertCanonicalEditorResources(rows) {
238
298
  if (rows.entities.filter((row) => row.entityKind === "timeline").length > 1) throw new EntityTimelineProjectionError("Editor requires exactly one Timeline");
@@ -263,8 +323,7 @@ function assertCanonicalEditorResources(rows) {
263
323
  *
264
324
  * 1. every Image/Video/Audio/Voice Entity carries its own Asset `external`
265
325
  * identity;
266
- * 2. no `physical-asset` Relation may bind a media variant (Caption inline
267
- * text keeps its inline Asset and binding);
326
+ * 2. no `physical-asset` Relation may bind a media variant or Caption;
268
327
  * 3. a self-carried media identity is immutable — it cannot be rewritten to
269
328
  * another asset id.
270
329
  *
@@ -298,6 +357,26 @@ function memotaLocator(value) {
298
357
  key: value.key
299
358
  };
300
359
  }
360
+ /**
361
+ * The project AudioScript is a fixed structure identity, like the Timeline: at
362
+ * most one may exist, and a missing one is minted atomically with empty
363
+ * segments. Project initialization and legacy migration share this single
364
+ * path, so both produce the same canonical structure.
365
+ */
366
+ function ensureDocumentAudioScript(entities, idFactory) {
367
+ const scripts = entities.filter((row) => row.entityKind === "audio-script");
368
+ if (scripts.length > 1) throw new EntityTimelineProjectionError("Editor requires exactly one AudioScript");
369
+ const existing = scripts[0];
370
+ if (existing !== void 0) return existing.entityId;
371
+ const id = createEntityId(idFactory("entity"));
372
+ if (entities.some((row) => row.entityId === id)) throw new Error(`Duplicate entity id ${id}`);
373
+ entities.push({
374
+ entityId: id,
375
+ entityKind: "audio-script",
376
+ payload: { segments: [] }
377
+ });
378
+ return id;
379
+ }
301
380
  /** Add only missing editor structure; existing identities and owned settings are never replaced. */
302
381
  function ensureEditorFoundation(initial, idFactory = (prefix) => `${prefix}_${globalThis.crypto.randomUUID()}`, timelinePayload = {}) {
303
382
  const entities = structuredClone([...initial.entities]);
@@ -350,13 +429,15 @@ function ensureEditorFoundation(initial, idFactory = (prefix) => `${prefix}_${gl
350
429
  }
351
430
  trackEntityIds[role] = track.entityId;
352
431
  }
432
+ const audioScriptEntityId = ensureDocumentAudioScript(entities, idFactory);
353
433
  return {
354
434
  rows: {
355
435
  entities,
356
436
  relations
357
437
  },
358
438
  timelineEntityId: timeline.entityId,
359
- trackEntityIds
439
+ trackEntityIds,
440
+ audioScriptEntityId
360
441
  };
361
442
  }
362
443
  //#endregion
@@ -366,7 +447,7 @@ const numericMarkerValidation$1 = { compareMarkerPoints: (_marker, _range, left,
366
447
  if (typeof left !== "number" || !Number.isFinite(left) || typeof right !== "number" || !Number.isFinite(right)) throw new Error("entity-state requires finite numeric Marker coordinates");
367
448
  return left < right ? -1 : left > right ? 1 : 0;
368
449
  } };
369
- /** Browser-safe authenticated CAS client for a document's Entity graph. */
450
+ /** Browser-safe authenticated Loro client for a document's Entity graph. */
370
451
  var EntityGraphHttpClient = class {
371
452
  options;
372
453
  transport;
@@ -381,30 +462,24 @@ var EntityGraphHttpClient = class {
381
462
  return this.request({ method: "GET" });
382
463
  }
383
464
  /**
384
- * Replace the complete Entity graph under the baseline revision.
465
+ * Compile edits against the original causal snapshot and publish native Loro operations.
385
466
  *
386
467
  * Deletion intent is derived from baseline IDs omitted from `nextRows`, so a
387
468
  * transient create-then-remove never becomes a request to delete unknown
388
469
  * persisted data.
389
470
  */
390
471
  async commit(baseState, nextRows, options = {}) {
391
- if (!isNonNegativeSafeInteger(baseState.revision)) throw new Error("Entity graph revision must be a non-negative safe integer");
392
- const baseRows = cloneAndValidateRows(baseState.rows);
472
+ if (options.migrationBaseVv !== void 0) throw new Error("Legacy entity migration is not supported by the Loro contract");
393
473
  const next = cloneAndValidateRows(nextRows);
394
- assertMediaAssetWritePolicy(baseRows, next);
395
- const nextEntityIds = new Set(next.entities.map((row) => row.entityId));
396
- const nextRelationIds = new Set(next.relations.map((row) => row.relationId));
397
- const deletedEntityIds = baseRows.entities.map((row) => row.entityId).filter((entityId) => !nextEntityIds.has(entityId)).sort();
398
- const deletedRelationIds = baseRows.relations.map((row) => row.relationId).filter((relationId) => !nextRelationIds.has(relationId)).sort();
474
+ assertMediaAssetWritePolicy(baseState.rows, next);
475
+ const compiled = compileEntityRows(base64ToBytes(baseState.loroSnapshot), next);
476
+ return this.commitUpdate(bytesToBase64(compiled.update));
477
+ }
478
+ /** Send the same prepared bytes on retry; never recompile an unconfirmed edit. */
479
+ async commitUpdate(update) {
399
480
  return this.request({
400
481
  method: "POST",
401
- body: JSON.stringify({
402
- expected_revision: baseState.revision,
403
- rows: toWireRows(next),
404
- deleted_entity_ids: deletedEntityIds,
405
- deleted_relation_ids: deletedRelationIds,
406
- ...options.migrationBaseVv === void 0 ? {} : { migration_base_vv: options.migrationBaseVv }
407
- })
482
+ body: JSON.stringify({ update })
408
483
  });
409
484
  }
410
485
  async request(init) {
@@ -422,10 +497,16 @@ function parseWireState(value, expectedDocId) {
422
497
  if (value.doc_id !== expectedDocId) throw new Error(`Entity-state response doc_id mismatch: expected "${expectedDocId}"`);
423
498
  const rows = parseWireRows(value.rows);
424
499
  decodeEntityRelationRows(rows, numericMarkerValidation$1);
425
- return {
500
+ if (typeof value.audio_script_entity_id !== "string") throw new Error("Document AudioScript is not initialized");
501
+ if (typeof value.loro_snapshot !== "string") throw new Error("Missing causal Loro snapshot");
502
+ const state = {
426
503
  revision: value.revision,
427
- rows
504
+ loroSnapshot: value.loro_snapshot,
505
+ rows,
506
+ audioScriptEntityId: value.audio_script_entity_id
428
507
  };
508
+ readDocumentAudioScript(state);
509
+ return state;
429
510
  }
430
511
  function parseWireRows(value) {
431
512
  if (!isRecord(value) || !Array.isArray(value.entities) || !Array.isArray(value.relations)) throw new Error("Invalid entity-state response rows");
@@ -458,23 +539,6 @@ function cloneAndValidateRows(rows) {
458
539
  decodeEntityRelationRows(clone, numericMarkerValidation$1);
459
540
  return clone;
460
541
  }
461
- function toWireRows(rows) {
462
- return {
463
- entities: rows.entities.map((row) => ({
464
- entity_id: row.entityId,
465
- entity_kind: row.entityKind,
466
- payload: row.payload
467
- })),
468
- relations: rows.relations.map((row) => ({
469
- relation_id: row.relationId,
470
- relation_kind: row.relationKind,
471
- endpoint_0_entity_id: row.endpoint0EntityId,
472
- endpoint_1_entity_id: row.endpoint1EntityId,
473
- metadata: row.metadata,
474
- trace: row.trace
475
- }))
476
- };
477
- }
478
542
  function requiredString(value, field) {
479
543
  if (typeof value !== "string" || value.length === 0 || value.trim() !== value) throw new Error(`Invalid ${field} in entity-state response`);
480
544
  return value;
@@ -608,6 +672,23 @@ function sameVoice(left, right) {
608
672
  }
609
673
  //#endregion
610
674
  //#region src/entity-editor/entity-timeline-editor.ts
675
+ /**
676
+ * Resolve the AudioScript a PhoneticScript composes. The composition is the
677
+ * required text source: a variant without it has no content and fails here.
678
+ */
679
+ function requireComposedAudioScript(rows, phoneticScriptEntityId) {
680
+ return assemblePhoneticScriptContent(rows, phoneticScriptEntityId).audioScript.entityId;
681
+ }
682
+ function captionSelections(selections) {
683
+ return structuredClone(selections);
684
+ }
685
+ function captionBaseIds(ids) {
686
+ if (!Array.isArray(ids) || ids.length === 0) throw new Error("Caption requires non-empty baseEntityIds");
687
+ return ids.map(createEntityId);
688
+ }
689
+ function assertCaptionScript(rows, captionId, scriptId) {
690
+ if (assembleCaptionContent(rows, captionId).audioScript.entityId !== scriptId) throw new Error("Voiceover Caption must compose the same AudioScript as the PhoneticScript");
691
+ }
611
692
  const roleOrder = {
612
693
  caption: 0,
613
694
  video_clip: 1,
@@ -1129,7 +1210,8 @@ var EntityTimelineEditor = class {
1129
1210
  const imported = importMediaAsset(draft, input.media, this.idFactory);
1130
1211
  replaceRows(draft, imported.rows);
1131
1212
  const voice = requireEntityKind(draft, imported.contentEntityId, "voice");
1132
- const script = this.ensureAudioScript(draft, voice.entityId, input.scriptText);
1213
+ const phonetic = this.requireComposedPhoneticScript(draft, input.phoneticScriptEntityId);
1214
+ this.ensurePhoneticRender(draft, voice.entityId, phonetic.entityId);
1133
1215
  const speechTrack = this.ensureRoleTrack(draft, input.timelineEntityId, "speech");
1134
1216
  const existing = draft.entities.find((row) => row.entityId === input.voiceoverClipEntityId);
1135
1217
  let voiceoverClipEntityId;
@@ -1179,12 +1261,13 @@ var EntityTimelineEditor = class {
1179
1261
  }
1180
1262
  });
1181
1263
  }
1182
- this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voice.entityId, script.entityId, input.captions);
1264
+ this.reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voice.entityId, phonetic.entityId, input.captions);
1183
1265
  this.resolveOneVoiceoverOverlap(draft);
1184
1266
  return {
1185
1267
  voiceoverClipEntityId,
1186
1268
  voiceEntityId: voice.entityId,
1187
- audioScriptEntityId: script.entityId,
1269
+ phoneticScriptEntityId: phonetic.entityId,
1270
+ audioScriptEntityId: requireComposedAudioScript(draft, phonetic.entityId),
1188
1271
  captionClipEntityIds: input.captions.map((caption) => caption.captionClipEntityId)
1189
1272
  };
1190
1273
  });
@@ -1268,33 +1351,30 @@ var EntityTimelineEditor = class {
1268
1351
  const track = findRoleTrack(draft, input.timelineEntityId, "caption");
1269
1352
  if (track === void 0) return;
1270
1353
  const patch = cloneJsonObject(input.style, "Caption style");
1354
+ const assembled = new EntityProjectionGraph(draft);
1271
1355
  for (const clip of clipsOnTrack(draft, track.entityId)) {
1272
1356
  const topology = requireClipTopology(draft, clip.entityId);
1273
1357
  const caption = requireEntityKind(draft, otherEndpoint(topology.markerContentRelation, topology.marker.entityId), "caption");
1274
- const prior = caption.payload.style;
1358
+ const prior = assembled.entity(caption.entityId).payload.style;
1275
1359
  const style = isJsonObject(prior) ? prior : {};
1276
- replaceEntity(draft, caption.entityId, {
1277
- ...caption,
1278
- payload: {
1279
- ...caption.payload,
1280
- style: {
1281
- ...style,
1282
- ...patch
1283
- }
1284
- }
1285
- });
1360
+ for (const updated of updateEntityFields(draft, caption.entityId, { style: {
1361
+ ...style,
1362
+ ...patch
1363
+ } })) replaceEntity(draft, updated.entityId, updated);
1286
1364
  }
1287
1365
  });
1288
1366
  }
1289
1367
  /**
1290
- * Place an independent Caption: its own content entity, inline text Asset,
1291
- * and Clip with a display Marker. No Voice, provenance, or alignment is
1292
- * required — the placement describes only this use. Moving or stretching
1293
- * the returned Clip later changes the Clip/Marker, never the Caption entity.
1368
+ * Place a Caption as a composed variant of an existing AudioScript: the
1369
+ * entity stores baseEntityIds, selection, optional style, and inherent cue-bounded
1370
+ * Sequence. This helper does not copy base text. The Clip and its display Marker describe this use;
1371
+ * moving or stretching the returned Clip later changes the Clip/Marker,
1372
+ * never the Caption entity or the AudioScript.
1294
1373
  */
1295
1374
  insertCaptionClip(input) {
1296
1375
  return this.transact((draft) => {
1297
1376
  const durationMs = finitePositive(input.durationMs, "Caption durationMs");
1377
+ const selections = captionSelections(input.selections);
1298
1378
  const track = this.ensureRoleTrack(draft, input.timelineEntityId, "caption");
1299
1379
  const content = {
1300
1380
  entityId: this.mintEntityId(draft),
@@ -1307,20 +1387,13 @@ var EntityTimelineEditor = class {
1307
1387
  },
1308
1388
  sampling: "native",
1309
1389
  coordinateSpace: "milliseconds",
1310
- text: input.text,
1390
+ baseEntityIds: captionBaseIds(input.baseEntityIds),
1391
+ selections,
1311
1392
  ...input.style === void 0 ? {} : { style: cloneJsonObject(input.style, "Caption style") }
1312
1393
  }
1313
1394
  };
1314
- const asset = {
1315
- entityId: this.mintEntityId(draft),
1316
- entityKind: "asset",
1317
- payload: { inline: {
1318
- mediaType: "text/plain",
1319
- text: input.text
1320
- } }
1321
- };
1322
- draft.entities.push(content, asset);
1323
- draft.relations.push(this.createRelation(draft, "physical-asset", content.entityId, asset.entityId));
1395
+ draft.entities.push(content);
1396
+ assembleCaptionContent(draft, content.entityId);
1324
1397
  return this.insertPlacedClipIntoDraft(draft, {
1325
1398
  trackEntityId: track.entityId,
1326
1399
  contentEntityId: content.entityId,
@@ -1398,34 +1471,24 @@ var EntityTimelineEditor = class {
1398
1471
  draft.relations.push(this.createRelation(draft, "timeline-track", timeline.entityId, track.entityId));
1399
1472
  return track;
1400
1473
  }
1401
- ensureAudioScript(draft, voiceEntityId, text) {
1402
- const renders = draft.relations.filter((relation) => relation.relationKind === "audio-script-render" && relation.endpoint0EntityId === voiceEntityId);
1403
- if (renders.length > 1) throw new Error(`Voice "${voiceEntityId}" has multiple AudioScript relations`);
1474
+ /** The existing pronunciation variant a Voice consumes; composition is required. */
1475
+ requireComposedPhoneticScript(draft, phoneticScriptEntityId) {
1476
+ const phonetic = requireEntityKind(draft, phoneticScriptEntityId, "phonetic-script");
1477
+ requireComposedAudioScript(draft, phonetic.entityId);
1478
+ return phonetic;
1479
+ }
1480
+ /** Preserve the factual synthesis input, regardless of persisted endpoint positions. */
1481
+ ensurePhoneticRender(draft, voiceEntityId, phoneticScriptEntityId) {
1482
+ const renders = draft.relations.filter((relation) => relation.relationKind === "phonetic-script-render" && (relation.endpoint0EntityId === voiceEntityId || relation.endpoint1EntityId === voiceEntityId));
1483
+ if (renders.length > 1) throw new Error(`Voice "${voiceEntityId}" has multiple PhoneticScript render relations`);
1404
1484
  if (renders[0] !== void 0) {
1405
- const script = requireEntityKind(draft, renders[0].endpoint1EntityId, "audio-script");
1406
- const replacement = {
1407
- ...script,
1408
- payload: { segments: [{
1409
- segmentId: "segment-0",
1410
- text
1411
- }] }
1412
- };
1413
- replaceEntity(draft, script.entityId, replacement);
1414
- return replacement;
1485
+ if (otherEndpoint(renders[0], voiceEntityId) === phoneticScriptEntityId) return;
1486
+ throw new Error(`Voice "${voiceEntityId}" already has a different PhoneticScript synthesis input`);
1415
1487
  }
1416
- const script = {
1417
- entityId: this.mintEntityId(draft),
1418
- entityKind: "audio-script",
1419
- payload: { segments: [{
1420
- segmentId: "segment-0",
1421
- text
1422
- }] }
1423
- };
1424
- draft.entities.push(script);
1425
- draft.relations.push(this.createRelation(draft, "audio-script-render", voiceEntityId, script.entityId));
1426
- return script;
1488
+ draft.relations.push(this.createRelation(draft, "phonetic-script-render", voiceEntityId, phoneticScriptEntityId));
1427
1489
  }
1428
- reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voiceEntityId, scriptEntityId, captions) {
1490
+ reconcileVoiceoverCaptions(draft, voiceoverClipEntityId, voiceEntityId, phoneticScriptEntityId, captions) {
1491
+ const audioScriptEntityId = requireComposedAudioScript(draft, phoneticScriptEntityId);
1429
1492
  const requested = new Set(captions.map((caption) => caption.captionClipEntityId));
1430
1493
  if (requested.size !== captions.length) throw new Error("Caption Clip ids must be unique within a voiceover take");
1431
1494
  const existing = directAnchorChildren(draft, voiceoverClipEntityId).filter((clipId) => clipRole(draft, clipId) === "caption");
@@ -1434,6 +1497,8 @@ var EntityTimelineEditor = class {
1434
1497
  for (const caption of captions) {
1435
1498
  const startMs = finiteNonNegative(caption.startMs, "Caption startMs");
1436
1499
  const durationMs = finitePositive(caption.durationMs, "Caption durationMs");
1500
+ const selections = captionSelections(caption.selections);
1501
+ const baseEntityIds = captionBaseIds(caption.baseEntityIds);
1437
1502
  if (draft.entities.find((entity) => entity.entityId === caption.captionClipEntityId) === void 0) {
1438
1503
  const content = {
1439
1504
  entityId: this.mintEntityId(draft),
@@ -1446,23 +1511,17 @@ var EntityTimelineEditor = class {
1446
1511
  },
1447
1512
  sampling: "native",
1448
1513
  coordinateSpace: "milliseconds",
1449
- text: caption.text,
1514
+ baseEntityIds,
1515
+ selections,
1450
1516
  ...caption.style === void 0 ? {} : { style: cloneJsonObject(caption.style, "Caption style") }
1451
1517
  }
1452
1518
  };
1453
- const asset = {
1454
- entityId: this.mintEntityId(draft),
1455
- entityKind: "asset",
1456
- payload: { inline: {
1457
- mediaType: "text/plain",
1458
- text: caption.text
1459
- } }
1460
- };
1461
- draft.entities.push(content, asset);
1462
- draft.relations.push(this.createRelation(draft, "physical-asset", content.entityId, asset.entityId), this.createRelation(draft, "caption-alignment", content.entityId, voiceEntityId, { alignment: {
1519
+ draft.entities.push(content);
1520
+ assertCaptionScript(draft, content.entityId, audioScriptEntityId);
1521
+ draft.relations.push(this.createRelation(draft, "caption-alignment", content.entityId, voiceEntityId, { alignment: {
1463
1522
  start: startMs,
1464
1523
  end: startMs + durationMs
1465
- } }), this.createRelation(draft, "caption-provenance", content.entityId, scriptEntityId, { segmentAlignment: [] }));
1524
+ } }));
1466
1525
  this.insertPlacedClipIntoDraft(draft, {
1467
1526
  clipEntityId: caption.captionClipEntityId,
1468
1527
  trackEntityId: captionTrack.entityId,
@@ -1494,27 +1553,12 @@ var EntityTimelineEditor = class {
1494
1553
  ...content,
1495
1554
  payload: {
1496
1555
  ...content.payload,
1497
- extent: {
1498
- kind: "bounded",
1499
- start: 0,
1500
- end: durationMs
1501
- },
1502
- text: caption.text,
1556
+ baseEntityIds,
1557
+ selections,
1503
1558
  ...Object.keys(style).length === 0 ? {} : { style }
1504
1559
  }
1505
1560
  });
1506
- const asset = requireEntityKind(draft, otherEndpoint(requireOneRelation(draft, content.entityId, "physical-asset"), content.entityId), "asset");
1507
- if (asset.payload.inline === void 0) throw new Error(`Caption "${caption.captionClipEntityId}" is not inline`);
1508
- replaceEntity(draft, asset.entityId, {
1509
- ...asset,
1510
- payload: {
1511
- ...asset.payload,
1512
- inline: {
1513
- mediaType: "text/plain",
1514
- text: caption.text
1515
- }
1516
- }
1517
- });
1561
+ assertCaptionScript(draft, content.entityId, audioScriptEntityId);
1518
1562
  this.applyClipPlacement(draft, topology.clip.entityId, {
1519
1563
  kind: "anchored",
1520
1564
  hostClipEntityId: voiceoverClipEntityId,
@@ -1525,17 +1569,13 @@ var EntityTimelineEditor = class {
1525
1569
  ...placed.marker,
1526
1570
  payload: {
1527
1571
  ...placed.marker.payload,
1528
- sourceRange: {
1529
- start: 0,
1530
- end: durationMs
1531
- },
1532
1572
  duration: {
1533
1573
  mode: "fixed",
1534
1574
  value: durationMs
1535
1575
  }
1536
1576
  }
1537
1577
  });
1538
- this.replaceCaptionSemanticRelations(draft, content.entityId, voiceEntityId, scriptEntityId, startMs, durationMs);
1578
+ this.replaceCaptionAlignment(draft, content.entityId, voiceEntityId, startMs, durationMs);
1539
1579
  }
1540
1580
  }
1541
1581
  ensureRoleTrackForClip(draft, clipEntityId, role) {
@@ -1544,34 +1584,30 @@ var EntityTimelineEditor = class {
1544
1584
  const timeline = requireEntityKind(draft, otherEndpoint(requireOneRelation(draft, track.entityId, "timeline-track"), track.entityId), "timeline");
1545
1585
  return this.ensureRoleTrack(draft, timeline.entityId, role);
1546
1586
  }
1547
- replaceCaptionSemanticRelations(draft, captionEntityId, voiceEntityId, scriptEntityId, startMs, durationMs) {
1548
- const removed = new Set(incidentRelations(draft, captionEntityId).filter((relation) => relation.relationKind === "caption-alignment" || relation.relationKind === "caption-provenance").map((relation) => relation.relationId));
1587
+ replaceCaptionAlignment(draft, captionEntityId, voiceEntityId, startMs, durationMs) {
1588
+ const removed = new Set(incidentRelations(draft, captionEntityId).filter((relation) => relation.relationKind === "caption-alignment").map((relation) => relation.relationId));
1549
1589
  draft.relations = draft.relations.filter((relation) => !removed.has(relation.relationId));
1550
1590
  draft.relations.push(this.createRelation(draft, "caption-alignment", captionEntityId, voiceEntityId, { alignment: {
1551
1591
  start: startMs,
1552
1592
  end: startMs + durationMs
1553
- } }), this.createRelation(draft, "caption-provenance", captionEntityId, scriptEntityId, { segmentAlignment: [] }));
1593
+ } }));
1554
1594
  }
1595
+ /**
1596
+ * Remove one placed Caption use. The composed AudioScript outlives the
1597
+ * caption: it may anchor further captions and remains the text owner.
1598
+ */
1555
1599
  removeCaptionClipGraph(draft, clipEntityId) {
1556
1600
  const topology = requireClipOnRole(draft, clipEntityId, "caption");
1557
1601
  const caption = requireEntityKind(draft, otherEndpoint(topology.markerContentRelation, topology.marker.entityId), "caption");
1558
- const asset = requireEntityKind(draft, otherEndpoint(requireOneRelation(draft, caption.entityId, "physical-asset"), caption.entityId), "asset");
1559
1602
  const contentRelations = incidentRelations(draft, caption.entityId);
1560
- const allowed = new Set([
1561
- "physical-asset",
1562
- "caption-alignment",
1563
- "caption-provenance",
1564
- "marker-content"
1565
- ]);
1603
+ const allowed = new Set(["caption-alignment", "marker-content"]);
1566
1604
  const unsupported = contentRelations.filter((relation) => !allowed.has(relation.relationKind));
1567
1605
  if (unsupported.length > 0) throw new Error(`Cannot delete Caption with unsupported Relation ${unsupported[0].relationId}`);
1568
1606
  this.removeClipGraph(draft, clipEntityId);
1569
1607
  if (incidentRelations(draft, caption.entityId).some((relation) => relation.relationKind === "marker-content")) return;
1570
1608
  const removed = new Set(incidentRelations(draft, caption.entityId).map((relation) => relation.relationId));
1571
- const assetIncidents = incidentRelations(draft, asset.entityId);
1572
- const deleteAsset = asset.payload.inline !== void 0 && assetIncidents.every((relation) => removed.has(relation.relationId));
1573
1609
  draft.relations = draft.relations.filter((relation) => !removed.has(relation.relationId));
1574
- draft.entities = draft.entities.filter((entity) => entity.entityId !== caption.entityId && (!deleteAsset || entity.entityId !== asset.entityId));
1610
+ draft.entities = draft.entities.filter((entity) => entity.entityId !== caption.entityId);
1575
1611
  }
1576
1612
  replaceAnchorHost(draft, childClipEntityId, hostClipEntityId) {
1577
1613
  const anchors = draft.relations.filter((relation) => relation.relationKind === "clip-anchor" && relation.endpoint0EntityId === childClipEntityId);
@@ -1964,6 +2000,7 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
1964
2000
  const normalizedMainRanges = overlapNormalizedMainRanges(legacy);
1965
2001
  let entities = structuredClone([...initial.entities]);
1966
2002
  let relations = structuredClone([...initial.relations]);
2003
+ const documentAudioScriptEntityId = ensureDocumentAudioScript(entities, idFactory);
1967
2004
  const addEntity = (kind, payload, existingId) => {
1968
2005
  const entityId = createEntityId(existingId ?? idFactory("entity"));
1969
2006
  if (entities.some((row) => row.entityId === entityId)) fail(`Migration entity identity collision: ${entityId}`);
@@ -1992,7 +2029,6 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
1992
2029
  facts.set(fact.assetId, fact);
1993
2030
  }
1994
2031
  const contentByPartId = /* @__PURE__ */ new Map();
1995
- const scriptBySpeechClipId = /* @__PURE__ */ new Map();
1996
2032
  for (const track of legacy.tracks ?? []) for (const item of track.items ?? []) {
1997
2033
  const part = requirePart(legacy, item.part_id);
1998
2034
  if (part.video_clip !== void 0) {
@@ -2026,7 +2062,8 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
2026
2062
  entities = [...imported.rows.entities];
2027
2063
  relations = [...imported.rows.relations];
2028
2064
  contentByPartId.set(item.part_id, imported.contentEntityId);
2029
- scriptBySpeechClipId.set(item.part_id, ensureAudioScript(imported.contentEntityId, speech.audio_script, entities, relations, addEntity, link));
2065
+ const phonemeScript = speech.phoneme_script;
2066
+ ensureSpeechPhoneticChain(imported.contentEntityId, speech.audio_script, typeof phonemeScript === "string" ? phonemeScript : void 0, entities, relations, addEntity, link, documentAudioScriptEntityId);
2030
2067
  } else if (part.bgm !== void 0) {
2031
2068
  const bgm = part.bgm;
2032
2069
  const fact = facts.get(bgm.origin_media_id);
@@ -2101,6 +2138,22 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
2101
2138
  } else if (part.caption !== void 0) {
2102
2139
  const caption = part.caption;
2103
2140
  duration = caption.initial_duration_ms;
2141
+ const voice = contentByPartId.get(caption.speech_part_id);
2142
+ const render = relations.find((relation) => relation.relationKind === "phonetic-script-render" && relation.endpoint0EntityId === voice);
2143
+ const phonetic = entities.find((entity) => entity.entityId === render?.endpoint1EntityId);
2144
+ const script = phonetic === void 0 ? void 0 : composedAudioScript(phonetic, entities);
2145
+ if (script === void 0) fail("Legacy Caption has no composed AudioScript");
2146
+ const sourceText = scriptText(script);
2147
+ const captionText = caption.text;
2148
+ const offset = sourceText.indexOf(captionText);
2149
+ if (!captionText.trim() || offset < 0 || sourceText.indexOf(captionText, offset + 1) >= 0) fail("Legacy Caption needs explicit unambiguous AudioScript selections before cutover");
2150
+ const selections = [{
2151
+ segmentId: "segment-0",
2152
+ ...sourceText === captionText ? {} : { textRange: {
2153
+ start: Array.from(sourceText.slice(0, offset)).length,
2154
+ end: Array.from(sourceText.slice(0, offset + captionText.length)).length
2155
+ } }
2156
+ }];
2104
2157
  content = addEntity("caption", {
2105
2158
  extent: {
2106
2159
  kind: "bounded",
@@ -2109,21 +2162,14 @@ function migrateLegacyTimelineToEntities(legacy, assetFacts, initial = {
2109
2162
  },
2110
2163
  sampling: "native",
2111
2164
  coordinateSpace: "milliseconds",
2112
- text: caption.text,
2165
+ baseEntityIds: [script.entityId],
2166
+ selections,
2113
2167
  ...caption.style === void 0 ? {} : { style: toEntityCaptionStyle(caption.style) }
2114
2168
  });
2115
- const asset = addEntity("asset", { inline: {
2116
- mediaType: "text/plain",
2117
- text: caption.text
2118
- } });
2119
- link("physical-asset", content, asset);
2120
- const voice = contentByPartId.get(caption.speech_part_id);
2121
- const script = scriptBySpeechClipId.get(caption.speech_part_id);
2122
2169
  link("caption-alignment", content, voice, { alignment: {
2123
2170
  start: caption.start_ms,
2124
2171
  end: caption.start_ms + duration
2125
2172
  } });
2126
- link("caption-provenance", content, script, { segmentAlignment: [] });
2127
2173
  markerPayload = {
2128
2174
  sourceRange: {
2129
2175
  start: 0,
@@ -2339,7 +2385,7 @@ function assertPartSupported(id, part, role) {
2339
2385
  if (speech.voice?.name !== void 0 && typeof speech.voice.name !== "string") fail("Migration requires a string voice name when present");
2340
2386
  if (!Array.isArray(speech.caption_ids) || speech.caption_ids.some((captionId) => !trimmed(captionId))) fail("Migration requires explicit Speech caption identities");
2341
2387
  if (new Set(speech.caption_ids).size !== speech.caption_ids.length) fail("Migration requires unique Speech caption identities");
2342
- if (phonemeScript !== void 0 && phonemeScript !== null) fail("Non-null phoneme_script requires a phonetic-script migration adapter");
2388
+ if (phonemeScript !== void 0 && phonemeScript !== null && typeof phonemeScript !== "string") fail("Migration requires a string phoneme_script when present");
2343
2389
  assertKnownFields(speech.voice, ["id", "name"], "voice");
2344
2390
  assertKnownFields(speech, [
2345
2391
  "id",
@@ -2463,20 +2509,43 @@ function requirePart(document, partId) {
2463
2509
  if (part === void 0) fail(`Placed Clip ${partId} has no part payload`);
2464
2510
  return part;
2465
2511
  }
2466
- function ensureAudioScript(voiceEntityId, text, entities, relations, addEntity, link) {
2467
- const renders = relations.filter((relation) => relation.relationKind === "audio-script-render" && relation.endpoint0EntityId === voiceEntityId);
2468
- if (renders.length > 1) fail(`Voice ${voiceEntityId} has ambiguous AudioScript provenance`);
2512
+ function ensureSpeechPhoneticChain(voiceEntityId, audioScriptText, phonemeScript, entities, relations, addEntity, link, documentAudioScriptEntityId) {
2513
+ const renders = relations.filter((relation) => relation.relationKind === "phonetic-script-render" && relation.endpoint0EntityId === voiceEntityId);
2514
+ if (renders.length > 1) fail(`Voice ${voiceEntityId} has ambiguous PhoneticScript provenance`);
2469
2515
  if (renders.length === 1) {
2470
- const script = entities.find((entity) => entity.entityId === renders[0].endpoint1EntityId && entity.entityKind === "audio-script");
2471
- if (script === void 0 || scriptText(script) !== text) fail(`Voice ${voiceEntityId} conflicts with the legacy Speech script`);
2472
- return script.entityId;
2516
+ const phonetic = entities.find((entity) => entity.entityId === renders[0].endpoint1EntityId && entity.entityKind === "phonetic-script");
2517
+ if (phonetic === void 0) fail(`Voice ${voiceEntityId} has a dangling PhoneticScript render`);
2518
+ const script = composedAudioScript(phonetic, entities);
2519
+ if (script === void 0 || scriptText(script) !== audioScriptText) fail(`Voice ${voiceEntityId} conflicts with the legacy Speech script`);
2520
+ return phonetic.entityId;
2521
+ }
2522
+ const script = entities.find((entity) => entity.entityId === documentAudioScriptEntityId);
2523
+ if (script === void 0 || script.entityKind !== "audio-script") fail("Migration must carry the fixed document AudioScript");
2524
+ const segments = script.payload.segments;
2525
+ if (Array.isArray(segments) && segments.length > 0) {
2526
+ if (scriptText(script) !== audioScriptText) fail(`Voice ${voiceEntityId} conflicts with the project AudioScript text`);
2527
+ } else {
2528
+ const scriptIndex = entities.findIndex((row) => row.entityId === script.entityId);
2529
+ entities[scriptIndex] = {
2530
+ ...script,
2531
+ payload: { segments: [{
2532
+ segmentId: "segment-0",
2533
+ text: audioScriptText
2534
+ }] }
2535
+ };
2473
2536
  }
2474
- const script = addEntity("audio-script", { segments: [{
2475
- segmentId: "segment-0",
2476
- text
2477
- }] });
2478
- link("audio-script-render", voiceEntityId, script);
2479
- return script;
2537
+ const phonetic = addEntity("phonetic-script", {
2538
+ baseEntityIds: [script.entityId],
2539
+ ...phonemeScript === void 0 ? {} : { phonemeScript }
2540
+ });
2541
+ link("phonetic-script-render", voiceEntityId, phonetic);
2542
+ return phonetic;
2543
+ }
2544
+ function composedAudioScript(phonetic, entities) {
2545
+ return findComposedAudioScript({
2546
+ entities,
2547
+ relations: []
2548
+ }, phonetic.entityId, "phonetic-script");
2480
2549
  }
2481
2550
  function scriptText(script) {
2482
2551
  const segments = script.payload.segments;
@@ -2566,6 +2635,82 @@ function fail(message) {
2566
2635
  throw new EntityTimelineProjectionError(message);
2567
2636
  }
2568
2637
  //#endregion
2638
+ //#region src/entity-editor/caption-content.ts
2639
+ /** Author Caption content from semantic intent; callers never reconcile variant bases or assembled fields. */
2640
+ function prepareCaptionContent(rows, input) {
2641
+ const entityId = createEntityId(input.contentEntityId);
2642
+ const scriptId = createEntityId(input.audioScriptEntityId);
2643
+ if (!Number.isFinite(input.durationMs) || input.durationMs <= 0) throw new Error("Caption duration must be positive");
2644
+ const previous = rows.entities.find((row) => row.entityId === entityId);
2645
+ if (previous !== void 0 && previous.entityKind !== "caption") throw new Error("Caption content identity has a different kind");
2646
+ let baseEntityIds = [scriptId];
2647
+ if (previous !== void 0) {
2648
+ const source = assembleCaptionContent(rows, entityId).audioScript.entityId;
2649
+ const bases = variantBaseEntityIds(previous);
2650
+ if (source !== scriptId && !bases.includes(source)) throw new Error("Replacing an indirectly composed AudioScript requires an explicit composition change");
2651
+ baseEntityIds = bases.map((id) => id === source ? scriptId : id);
2652
+ }
2653
+ const candidate = {
2654
+ entityId,
2655
+ entityKind: "caption",
2656
+ payload: {
2657
+ ...previous?.payload,
2658
+ baseEntityIds,
2659
+ extent: previous?.payload.extent ?? {
2660
+ kind: "bounded",
2661
+ start: 0,
2662
+ end: input.durationMs
2663
+ },
2664
+ sampling: previous?.payload.sampling ?? "native",
2665
+ coordinateSpace: previous?.payload.coordinateSpace ?? "milliseconds"
2666
+ }
2667
+ };
2668
+ const graph = {
2669
+ ...rows,
2670
+ entities: [...rows.entities.filter((row) => row.entityId !== entityId), candidate]
2671
+ };
2672
+ const textSource = {
2673
+ ...assembleEntityContent(graph, entityId),
2674
+ entityKind: "audio-script"
2675
+ };
2676
+ const selected = {
2677
+ ...candidate,
2678
+ payload: {
2679
+ ...candidate.payload,
2680
+ selections: structuredClone(input.selections ?? selectCaptionText(textSource, input.text))
2681
+ }
2682
+ };
2683
+ if (assembleCaptionContent({
2684
+ ...graph,
2685
+ entities: graph.entities.map((row) => row.entityId === entityId ? selected : row)
2686
+ }, entityId).text !== input.text) throw new Error("Caption review text differs from its assembled variant content");
2687
+ return selected;
2688
+ }
2689
+ function selectCaptionText(script, text) {
2690
+ if (!text.trim()) throw new Error("Caption text selection must not be blank");
2691
+ const source = script;
2692
+ const fullText = assembleScriptText(source);
2693
+ const segments = source.payload.segments;
2694
+ if (text === fullText) return segments.map((segment) => ({ segmentId: segment.segmentId }));
2695
+ const matches = [];
2696
+ for (const segment of segments) {
2697
+ let offset = segment.text.indexOf(text);
2698
+ while (offset !== -1) {
2699
+ const start = Array.from(segment.text.slice(0, offset)).length;
2700
+ matches.push({
2701
+ segmentId: segment.segmentId,
2702
+ textRange: {
2703
+ start,
2704
+ end: start + Array.from(text).length
2705
+ }
2706
+ });
2707
+ offset = segment.text.indexOf(text, offset + 1);
2708
+ }
2709
+ }
2710
+ if (matches.length !== 1) throw new Error("Caption text must select a unique AudioScript range; provide explicit selections");
2711
+ return matches;
2712
+ }
2713
+ //#endregion
2569
2714
  //#region src/editor/snapshot-utils.ts
2570
2715
  function isMap(value) {
2571
2716
  return value instanceof Map;
@@ -4704,4 +4849,4 @@ function serverCovers(serverVV, target) {
4704
4849
  return covers(server, target);
4705
4850
  }
4706
4851
  //#endregion
4707
- export { DEFAULT_UNIT_TIME_MS, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, EntityGraphHttpClient, EntityTimelineEditor, EntityTimelineProjectionError, IMPLEMENTED_SEMANTIC_OP_KINDS, LANE_KINDS_IN_STACK_ORDER, ManualSyncDoc, MedeoHttpDocStorage, MemoryDocStorage, MengineAckFailedError, MengineDocSession, MengineHttpClient, MengineHttpRequestError, MenginePushRejectedError, MirrorVideoDocumentAdapter, PlainMemoryAdapter, SchemaValidator, SemanticEditor, TIMELINE_SKELETON_DURATION_MS, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, VideoDocumentValidationError, applyEntityTimelineProjection, arrangeMainTrackSeamlessly, assertCanonicalEditorResources, assertEntityProjectionPreservesLegacy, assertMediaAssetWritePolicy, assertValidVideoDocument, base64ToBytes, buildInitialVideoDocument, buildSpeechHostMap, bytesToBase64, cascadeAfterVideoClipChanges, createEditSandbox, createMirrorVideoDocument, createMirrorVideoDocumentAdapter, createPlainMemoryAdapter, decodeDocVersionMark, derivePositionFromAbs, effectiveVideoClipDurationMs, encodeDocVersionMark, ensureEditorFoundation, ensureLaneTrack, fillMainTrackTimeGaps, findLaneTrack, fromVideoDocument, generatePartId, getAt, hostForAbsMs, importMediaAsset, isEmptyVideoClip, isImplementedSemanticOpKind, isMap, laneTrackId, mainTrackRanges, migrateLegacyTimelineToEntities, partDurationMs, partUnionSchema, projectEntityTimeline, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, replayJournal, resolveAllSpeechOverlaps, resolveEntityTimelineLayout, resolveSpeechOverlapByShiftingVideos, safeDurationMs, schemas_exports as schemas, snapshotToPlain, solveVideoDocument, speedOf, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema, writeVideoDocumentToDraft };
4852
+ export { DEFAULT_UNIT_TIME_MS, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION, 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, VIDEO_DOCUMENT_SCHEMA_VERSION, ValidationError, VideoDocumentValidationError, applyEntityRows, applyEntityTimelineProjection, arrangeMainTrackSeamlessly, assembleCaptionContent, assembleEntityContent, assemblePhoneticScriptContent, assembleScriptText, assertCanonicalEditorResources, assertEntityProjectionPreservesLegacy, assertMediaAssetWritePolicy, assertValidVideoDocument, 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, prepareCaptionContent, projectEntityTimeline, readDocumentAudioScript, readEntityDocument, readMainTrackItems, readMengineEventStream, readPart, readPartDurationMs, readVideoDocumentFromDraft, reassignSpeechesToVideoClipsByTime, recalculateTimelineDuration, relativePositionForAbs, replayJournal, resolveAllSpeechOverlaps, resolveEntityTimelineLayout, resolveSpeechOverlapByShiftingVideos, safeDurationMs, schemas_exports as schemas, selectAudioScriptSegments, snapshotToPlain, solveVideoDocument, speedOf, syncAggregatedClipsTimePosition, toVideoDocument, validateVideoDocument, videoDocumentMirrorSchema, videoDocumentSchema, writeVideoDocumentToDraft };