@mengine/medeo-client 2.0.1-alpha.6 → 2.0.1-alpha.8

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.
@@ -1,5 +1,5 @@
1
1
  import { u as volumeSchema } from "./shared-iWnU2osE.js";
2
- import { Mirror, schema } from "loro-mirror";
2
+ import { Mirror, schema, validateSchema } from "loro-mirror";
3
3
  import { z } from "zod";
4
4
  import { LoroDoc, LoroMap, LoroMovableList, LoroText, UndoManager } from "loro-crdt";
5
5
  import { sha256 } from "@noble/hashes/sha2.js";
@@ -7,9 +7,11 @@ import { bytesToHex } from "@noble/hashes/utils.js";
7
7
  import { produce } from "immer";
8
8
  //#region src/client/base64.ts
9
9
  function bytesToBase64(bytes) {
10
- let binary = "";
11
- for (const byte of bytes) binary += String.fromCharCode(byte);
12
- return btoa(binary);
10
+ const native = bytes.toBase64;
11
+ if (typeof native === "function") return native.call(bytes);
12
+ const chunks = [];
13
+ for (let offset = 0; offset < bytes.length; offset += 8192) chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + 8192)));
14
+ return btoa(chunks.join(""));
13
15
  }
14
16
  function base64ToBytes(base64) {
15
17
  const binary = atob(base64);
@@ -47,10 +49,6 @@ function base64ToBytes(base64) {
47
49
  * value, last-writer-wins). The derived `fallback_abs_ms` snapshot sits beside
48
50
  * it as its own number field — a separate LWW unit so a projection refresh
49
51
  * never clobbers a `time_position` edit.
50
- * - meta lives in a `meta` LoroMap: the mirror root only accepts container
51
- * schemas, so meta scalars can't sit at the root as bare values. The domain
52
- * `VideoDocument` keeps the same `meta` wrapper, so the shapes are isomorphic
53
- * and the read/seed mapping is a near-identity.
54
52
  * - `part_library` values are nested `LoroMap`s: the value is a `LoroMap` with
55
53
  * four mutually-exclusive optional part sub-maps (`video_clip` / `speech` /
56
54
  * `caption` / `bgm`), each a `LoroMap` whose fields are stored as real Loro
@@ -64,16 +62,10 @@ function base64ToBytes(base64) {
64
62
  * as opaque JSON-blob sub-keys via `transform`. The four sub-keys are optional,
65
63
  * so the "exactly one part kind" union invariant is not enforced by the schema
66
64
  * type; it is rebuilt by `draftToPartUnion` on read and gated by zod on write.
67
- * - a caption's `style` is the one lazily-created optional child container, so
68
- * `captionPart` sets `mergeableMapChildContainers: true` — two peers can
69
- * concurrently first-create the same caption's `style` (one attribute each), and
70
- * only a mergeable child converges instead of last-writer-wins dropping one. All
71
- * other nested maps are keyed by a unique id (`part_library`) or created
72
- * atomically with their parent (`partValue`'s part sub-map), so they never hit
73
- * that concurrent-first-create fork and stay on plain `setContainer`. See the
74
- * note on `captionPart` below.
75
- * - `video_creation_settings` is an opaque JSON blob carried in a string via
76
- * `transform`: a whole value, last-writer-wins (no field-level concurrent edits).
65
+ * - a caption's `style` is a mergeable child map for independent style fields.
66
+ * Creation paths materialize it with the caption, even when empty, so undoing
67
+ * a later style edit never removes the parent reference. Other nested maps
68
+ * keep their regular container identities from creation.
77
69
  */
78
70
  /**
79
71
  * JSON-blob transform for an opaque, last-writer-wins value carried in a Loro
@@ -154,16 +146,6 @@ const partValue = schema.LoroMap({
154
146
  bgm: bgmPart
155
147
  });
156
148
  const videoDocumentMirrorSchema = schema({
157
- meta: schema.LoroMap({
158
- schema_version: schema.String({ required: false }),
159
- draft_id: schema.String({ required: false }),
160
- project_id: schema.String({ required: false }),
161
- owner_id: schema.String({ required: false }),
162
- thumbnail_storage_key: schema.String({ required: false }),
163
- chat_session_id: schema.String({ required: false }),
164
- video_creation_settings: schema.String({ required: false }).transform(jsonTransform()),
165
- version: schema.Number({ required: false })
166
- }),
167
149
  timeline: schema.LoroMap({ unit_time_ms: schema.Number({ required: false }) }),
168
150
  tracks: schema.LoroMovableList(track, (t) => t.id),
169
151
  part_library: schema.LoroMapRecord(partValue)
@@ -213,9 +195,6 @@ function recordEntries(record) {
213
195
  }
214
196
  //#endregion
215
197
  //#region src/document/types.ts
216
- const VIDEO_DOCUMENT_SCHEMA_VERSION = "video-document/v0";
217
- /** A server-derived compatibility view, never an editor write authority. */
218
- const ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION = "video-document/entity-projection-v1";
219
198
  /** The linear speed multiplier of a `speed_shift`, defaulting to 1 (original). */
220
199
  function speedOf(speedShift) {
221
200
  const speed = speedShift?.config?.linear?.speed;
@@ -549,12 +528,12 @@ function cascadeAfterVideoClipChanges(doc, makeEmptyPart) {
549
528
  * read-view: absolute time per item, `part_aggregations`, and total duration.
550
529
  * This is the read side of the single-directional flow — never written back.
551
530
  */
552
- function solveVideoDocument(document) {
531
+ function solveVideoDocument(document, options = {}) {
553
532
  const doc = videoDocumentToTimelineDoc(document);
554
533
  let counter = 0;
555
534
  const derivedFillerPartIds = /* @__PURE__ */ new Set();
556
- if (document.meta.schema_version === "video-document/entity-projection-v1") {
557
- for (const track of document.tracks ?? []) if (track.items?.some((item) => item.time_position.mode === "sequential" || item.time_position.mode === "anchored" && !Number.isSafeInteger(item.fallback_abs_ms))) throw new Error("An entity timeline projection must contain resolved placement facts");
535
+ if (options.placementResolved === true) {
536
+ assertResolvedPlacement(document);
558
537
  recalculateTimelineDuration(doc);
559
538
  } else cascadeAfterVideoClipChanges(doc, () => {
560
539
  const partId = `empty_${counter++}`;
@@ -759,6 +738,19 @@ function ensureLaneTrack(draft, kind) {
759
738
  function findLaneTrack(draft, kind) {
760
739
  return (draft.tracks ?? []).find((t) => t?.parts_kind === kind);
761
740
  }
741
+ /**
742
+ * Whether every item already states a position that can be read without running
743
+ * the cascade: nothing `sequential`, and every `anchored` item carrying the
744
+ * absolute landing its writer stamped. This is a verification predicate, not a
745
+ * provenance test — see `SolveVideoDocumentOptions.placementResolved`.
746
+ */
747
+ function hasResolvedPlacement(document) {
748
+ return (document.tracks ?? []).every((track) => (track.items ?? []).every((item) => item.time_position.mode !== "sequential" && (item.time_position.mode !== "anchored" || Number.isSafeInteger(item.fallback_abs_ms))));
749
+ }
750
+ /** A producer that claims resolved placement must actually have resolved it. */
751
+ function assertResolvedPlacement(document) {
752
+ if (!hasResolvedPlacement(document)) throw new Error("An entity timeline projection must contain resolved placement facts");
753
+ }
762
754
  //#endregion
763
755
  //#region src/document/zod-schema.ts
764
756
  const partKindSchema = z.enum([
@@ -859,18 +851,7 @@ const partUnionSchema = z.union([
859
851
  "bgm"
860
852
  ].filter((k) => p[k] != null).length === 1;
861
853
  }, { message: "A part_library value must have exactly one of video_clip / speech / caption / bgm" });
862
- const videoDocumentMetaSchema = z.object({
863
- schema_version: z.enum([VIDEO_DOCUMENT_SCHEMA_VERSION, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION]),
864
- draft_id: z.string().optional(),
865
- project_id: z.string().optional(),
866
- owner_id: z.string().optional(),
867
- thumbnail_storage_key: z.string().optional(),
868
- chat_session_id: z.string().optional(),
869
- video_creation_settings: z.unknown().optional(),
870
- version: finiteNumber.optional()
871
- }).passthrough();
872
854
  const videoDocumentSchema = z.object({
873
- meta: videoDocumentMetaSchema,
874
855
  timeline: z.object({ unit_time_ms: finiteNumber.optional() }).passthrough().optional(),
875
856
  tracks: z.array(trackSchema).optional(),
876
857
  part_library: z.record(z.string(), partUnionSchema).optional()
@@ -1140,13 +1121,13 @@ function zodPath(path) {
1140
1121
  //#endregion
1141
1122
  //#region src/document/projection.ts
1142
1123
  /**
1143
- * Projection between the authoritative `VideoDocument` and the legacy
1144
- * `VideoDraft` read-view (RFC 02 §5/§7). Both directions live here:
1124
+ * Projection between authoritative `VideoDocument` and compatible content
1125
+ * layouts (RFC 02 §5/§7). Both directions live here:
1145
1126
  *
1146
1127
  * - `toVideoDocument` ingests a `VideoDraft`, deriving each item's `position`
1147
1128
  * from the legacy absolute layout + aggregations; derived values (abs time,
1148
1129
  * `part_aggregations`, total duration) are dropped.
1149
- * - `fromVideoDocument` solves a `VideoDocument` back into a `VideoDraft` via the
1130
+ * - `fromVideoDocument` solves a `VideoDocument` into `VideoDraftContent` via the
1150
1131
  * timeline-core cascade, re-deriving exactly those values.
1151
1132
  *
1152
1133
  * Business validation lives in `validation.ts`; `fromVideoDocument` asserts a
@@ -1177,16 +1158,6 @@ function toVideoDocument(draft) {
1177
1158
  ...(draft.below_main_tracks ?? []).map((t) => toTrack(t, false))
1178
1159
  ];
1179
1160
  return {
1180
- meta: {
1181
- schema_version: VIDEO_DOCUMENT_SCHEMA_VERSION,
1182
- draft_id: draft.id,
1183
- project_id: draft.project_id,
1184
- owner_id: draft.owner_id,
1185
- thumbnail_storage_key: draft.thumbnail_storage_key,
1186
- chat_session_id: draft.chat_session_id,
1187
- video_creation_settings: clone$1(draft.video_creation_settings),
1188
- version: draft.version
1189
- },
1190
1161
  timeline: draft.timeline == null ? void 0 : { unit_time_ms: draft.timeline.unit_time_ms },
1191
1162
  tracks,
1192
1163
  part_library: toAuthoritativePartLibrary(draft.part_library)
@@ -1296,8 +1267,8 @@ function deriveItem(item, isMain, speechHost, partLibrary) {
1296
1267
  };
1297
1268
  }
1298
1269
  /**
1299
- * Project the authoritative `VideoDocument` back into the legacy `VideoDraft`
1300
- * read-view, solving each item's absolute position, the `part_aggregations`, and
1270
+ * Project `VideoDocument` into `VideoDraftContent`, solving each item's
1271
+ * absolute position, the `part_aggregations`, and
1301
1272
  * the total duration via the timeline-core cascade.
1302
1273
  *
1303
1274
  * The read-view is a compatibility contract, and the IDL declares
@@ -1311,33 +1282,26 @@ function deriveItem(item, isMain, speechHost, partLibrary) {
1311
1282
  * These two are defaultable because the projection knows their values on its own:
1312
1283
  * absent `unit_time_ms` means "no display granularity was ever stated" and absent
1313
1284
  * `is_hidden` means "this track was never hidden". `version` is NOT defaultable
1314
- * here — its only honest value is server state (`update_seq`) that a
1315
- * `VideoDocument` cannot see, so it stays absent and the HTTP layer fills it.
1285
+ * here — it belongs to Director assembly using the server envelope update_seq.
1286
+ * Neither business metadata nor a revision counter belongs in content projection.
1316
1287
  */
1317
- function fromVideoDocument(document) {
1288
+ function fromVideoDocument(document, options = {}) {
1318
1289
  assertValidVideoDocument(document);
1319
- const view = solveVideoDocument(document);
1290
+ const view = solveVideoDocument(document, options);
1320
1291
  const tracks = document.tracks ?? [];
1321
1292
  const mainTrack = tracks.find((t) => t.parts_kind === "video_clip");
1322
1293
  const aboveTracks = tracks.filter((t) => t.parts_kind === "caption");
1323
1294
  const belowTracks = tracks.filter((t) => t.parts_kind === "speech" || t.parts_kind === "bgm");
1324
1295
  return {
1325
- id: document.meta.draft_id,
1326
- project_id: document.meta.project_id,
1327
- owner_id: document.meta.owner_id,
1328
- thumbnail_storage_key: document.meta.thumbnail_storage_key,
1329
1296
  timeline: {
1330
1297
  duration_ms: view.durationMs,
1331
1298
  unit_time_ms: document.timeline?.unit_time_ms ?? 33.333
1332
1299
  },
1333
- video_creation_settings: clone$1(document.meta.video_creation_settings),
1334
- chat_session_id: document.meta.chat_session_id,
1335
1300
  main_track: draftTrack(mainTrack, view.absByPartId),
1336
1301
  above_main_tracks: aboveTracks.map((t) => draftTrack(t, view.absByPartId)),
1337
1302
  below_main_tracks: belowTracks.map((t) => draftTrack(t, view.absByPartId)),
1338
1303
  part_aggregations: view.aggregations,
1339
- part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds),
1340
- version: document.meta.version
1304
+ part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds)
1341
1305
  };
1342
1306
  }
1343
1307
  /**
@@ -1444,7 +1408,7 @@ function clone$1(value) {
1444
1408
  //#region src/document/initial-document.ts
1445
1409
  /**
1446
1410
  * Build the `VideoDocument` a newly created project starts from: the caller's
1447
- * project facts, no parts, and one empty track per lane.
1411
+ * no parts and one empty track per lane. Business facts remain in Director.
1448
1412
  *
1449
1413
  * The empty lane tracks are the reason this function exists rather than callers
1450
1414
  * assembling a document inline. Lane tracks are otherwise minted lazily by the
@@ -1465,18 +1429,8 @@ function clone$1(value) {
1465
1429
  * supplies, and total duration is derived on read (RFC 02 §6), so a new document
1466
1430
  * has no timeline fact to state.
1467
1431
  */
1468
- function buildInitialVideoDocument(facts) {
1432
+ function buildInitialVideoDocument() {
1469
1433
  return {
1470
- meta: {
1471
- schema_version: VIDEO_DOCUMENT_SCHEMA_VERSION,
1472
- draft_id: facts.draftId,
1473
- project_id: facts.projectId,
1474
- owner_id: facts.ownerId,
1475
- thumbnail_storage_key: void 0,
1476
- chat_session_id: facts.chatSessionId,
1477
- video_creation_settings: facts.videoCreationSettings,
1478
- version: void 0
1479
- },
1480
1434
  timeline: void 0,
1481
1435
  tracks: emptyLaneTracks(),
1482
1436
  part_library: {}
@@ -1493,8 +1447,63 @@ function emptyLaneTracks() {
1493
1447
  }
1494
1448
  //#endregion
1495
1449
  //#region ../medeo-dsl/src/entities.ts
1450
+ /**
1451
+ * The declared trait set of every known kind.
1452
+ *
1453
+ * `caption` carries `sequence` on purpose: a Caption owns the display timing of
1454
+ * the one AudioScript segment it shows, which is what admits it into a Clip.
1455
+ * `voice` carries no media or sequence trait — it is a timbre identity, not
1456
+ * playable content; a rendered voiceover is an `audio` entity.
1457
+ */
1458
+ const ENTITY_TRAIT_KINDS = Object.freeze({
1459
+ axvideo: Object.freeze([
1460
+ "container",
1461
+ "sequence",
1462
+ "visual",
1463
+ "audible"
1464
+ ]),
1465
+ timeline: Object.freeze(["container"]),
1466
+ track: Object.freeze(["container", "sequence"]),
1467
+ clip: Object.freeze(["container"]),
1468
+ asset: Object.freeze([]),
1469
+ video: Object.freeze([
1470
+ "media-file",
1471
+ "sequence",
1472
+ "visual",
1473
+ "audible"
1474
+ ]),
1475
+ audio: Object.freeze([
1476
+ "media-file",
1477
+ "sequence",
1478
+ "audible"
1479
+ ]),
1480
+ image: Object.freeze([
1481
+ "media-file",
1482
+ "sequence",
1483
+ "visual"
1484
+ ]),
1485
+ voice: Object.freeze(["container", "ip-bound"]),
1486
+ "sequence-marker": Object.freeze([]),
1487
+ viewport: Object.freeze([]),
1488
+ "audio-script": Object.freeze(["segment-text"]),
1489
+ "phonetic-script": Object.freeze(["segment-text"]),
1490
+ caption: Object.freeze(["segment-text", "sequence"])
1491
+ });
1492
+ /** Declared traits of a known kind; extension kinds declare none through this table. */
1493
+ function traitKindsOf(entityKind) {
1494
+ return isKnownEntityKind(entityKind) ? ENTITY_TRAIT_KINDS[entityKind] : [];
1495
+ }
1496
+ function hasTraitKind(entityKind, trait) {
1497
+ return traitKindsOf(entityKind).includes(trait);
1498
+ }
1499
+ /**
1500
+ * Sequence admission asks the declared traits first, so a kind that does not
1501
+ * declare `sequence` can never buy its way in by carrying the fields. Extension
1502
+ * kinds declare no traits and stay structurally detected.
1503
+ */
1496
1504
  function hasSequence(entity) {
1497
- if (isKnownNonSequenceKind(entity.entityKind) || isReservedEntityKind(entity.entityKind)) return false;
1505
+ if (isReservedEntityKind(entity.entityKind)) return false;
1506
+ if (isKnownEntityKind(entity.entityKind) && !hasTraitKind(entity.entityKind, "sequence")) return false;
1498
1507
  return isSequenceFields(entity);
1499
1508
  }
1500
1509
  function isSequenceFields(value) {
@@ -1507,22 +1516,16 @@ function isSequenceFields(value) {
1507
1516
  if (value.sampling !== "native" && value.sampling !== "constant" && value.sampling !== "derived") return false;
1508
1517
  return "coordinateSpace" in value && value.coordinateSpace !== void 0;
1509
1518
  }
1510
- function isKnownSequenceKind(kind) {
1511
- return isMediaAssetVariantKind(kind) || kind === "caption" || kind === "axvideo";
1512
- }
1513
1519
  function isMediaAssetVariantKind(kind) {
1514
- return kind === "video" || kind === "image" || kind === "audio" || kind === "voice";
1520
+ return kind === "video" || kind === "image" || kind === "audio";
1515
1521
  }
1516
1522
  function isKnownEntityKind(kind) {
1517
- return isKnownSequenceKind(kind) || isKnownNonSequenceKind(kind);
1523
+ return Object.hasOwn(ENTITY_TRAIT_KINDS, kind);
1518
1524
  }
1519
1525
  function isReservedEntityKind(kind) {
1520
1526
  const normalized = kind.toLowerCase().replaceAll("-", "").replaceAll("_", "");
1521
1527
  return normalized === "speech" || normalized === "videodocument";
1522
1528
  }
1523
- function isKnownNonSequenceKind(kind) {
1524
- return kind === "timeline" || kind === "track" || kind === "clip" || kind === "asset" || kind === "sequence-marker" || kind === "viewport" || kind === "audio-script" || kind === "phonetic-script";
1525
- }
1526
1529
  function isRecord$1(value) {
1527
1530
  return typeof value === "object" && value != null && !Array.isArray(value);
1528
1531
  }
@@ -2105,12 +2108,15 @@ function validateKnownEntityPayload(entity) {
2105
2108
  break;
2106
2109
  case "video":
2107
2110
  case "audio":
2108
- case "voice":
2111
+ validateSequencePayload(value, "bounded", "native", problems);
2112
+ validateMediaAssetFields(value, problems);
2113
+ break;
2109
2114
  case "caption":
2110
2115
  validateSequencePayload(value, "bounded", "native", problems);
2111
- if (entity.entityKind !== "caption") validateMediaAssetFields(value, problems);
2112
- if (entity.entityKind === "voice") validateVoicePayload(value, problems);
2113
- if (entity.entityKind === "caption") validateCaptionPayload(value, problems);
2116
+ validateCaptionPayload(value, problems);
2117
+ break;
2118
+ case "voice":
2119
+ validateVoicePayload(value, problems);
2114
2120
  break;
2115
2121
  case "image":
2116
2122
  validateSequencePayload(value, "unbounded", "constant", problems);
@@ -2217,11 +2223,26 @@ function validateExternalLocator(value, problems) {
2217
2223
  function validateStorageKey(value, problems) {
2218
2224
  if (value.storageKey !== void 0 && (typeof value.storageKey !== "string" || value.storageKey.trim() === "")) problems.push("storageKey must be a non-empty string when present");
2219
2225
  }
2226
+ /**
2227
+ * Voice is a timbre identity, not playable content: it owns the voice-library
2228
+ * descriptor and nothing that would make it look like media or a Sequence. The
2229
+ * rendered voiceover is an `audio` entity bound by a `voice-timbre` Relation.
2230
+ */
2220
2231
  function validateVoicePayload(value, problems) {
2232
+ for (const key of [
2233
+ "extent",
2234
+ "sampling",
2235
+ "coordinateSpace",
2236
+ "external",
2237
+ "storageKey"
2238
+ ]) if (Object.hasOwn(value, key)) problems.push(`${key} belongs to the rendered Audio, not to the Voice identity`);
2221
2239
  const voice = value.voice;
2222
- if (voice === void 0) return;
2240
+ if (voice === void 0) {
2241
+ problems.push("voice is required; a Voice entity is its voice-library identity");
2242
+ return;
2243
+ }
2223
2244
  if (!isRecord(voice)) {
2224
- problems.push("voice must be an object when present");
2245
+ problems.push("voice must be an object");
2225
2246
  return;
2226
2247
  }
2227
2248
  if (voice.system !== "voice-library") problems.push("voice.system must be \"voice-library\"");
@@ -2353,7 +2374,6 @@ function isOwnedLocalEntityValuePath(entityKind, path) {
2353
2374
  if ([
2354
2375
  "video",
2355
2376
  "audio",
2356
- "voice",
2357
2377
  "image",
2358
2378
  "caption",
2359
2379
  "axvideo"
@@ -2417,7 +2437,7 @@ function collectRelatedEntities(entityRefs, index) {
2417
2437
  };
2418
2438
  }
2419
2439
  function expectedSequenceShape(kind) {
2420
- if (kind === "video" || kind === "audio" || kind === "voice" || kind === "caption") return {
2440
+ if (kind === "video" || kind === "audio" || kind === "caption") return {
2421
2441
  extent: "bounded",
2422
2442
  sampling: "native"
2423
2443
  };
@@ -2458,7 +2478,7 @@ const generatedRelationSpec = Object.freeze({
2458
2478
  });
2459
2479
  const captionAlignmentRelationSpec = Object.freeze({
2460
2480
  kind: "caption-alignment",
2461
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio", "voice"])),
2481
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio"])),
2462
2482
  validateMetadata: isCaptionAlignmentMetadata
2463
2483
  });
2464
2484
  /** `clip-anchor(child, host)` means endpoint 0 follows endpoint 1. */
@@ -2467,20 +2487,29 @@ const clipAnchorRelationSpec = Object.freeze({
2467
2487
  validateEndpoints: (endpoints) => endpoints[0].current().entityKind === "clip" && endpoints[1].current().entityKind === "clip",
2468
2488
  validateMetadata: isEmptyMetadata
2469
2489
  });
2470
- /** Voice is generated from PhoneticScript; kinds determine roles regardless of endpoint positions. */
2490
+ /**
2491
+ * The rendered voiceover Audio and the PhoneticScript it was synthesized from;
2492
+ * kinds determine roles regardless of endpoint positions.
2493
+ */
2471
2494
  const phoneticScriptRenderRelationSpec = Object.freeze({
2472
2495
  kind: "phonetic-script-render",
2473
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["voice"]), new Set(["phonetic-script"])),
2496
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio"]), new Set(["phonetic-script"])),
2497
+ validateMetadata: isEmptyMetadata
2498
+ });
2499
+ /**
2500
+ * The timbre identity a rendered voiceover Audio was synthesized with. Voice
2501
+ * stays an identity: it never carries the audio itself, so the link between the
2502
+ * two is a Relation rather than one shared entity.
2503
+ */
2504
+ const voiceTimbreRelationSpec = Object.freeze({
2505
+ kind: "voice-timbre",
2506
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio"]), new Set(["voice"])),
2474
2507
  validateMetadata: isEmptyMetadata
2475
2508
  });
2476
2509
  /** AudioScript was transcribed from Audio, Video, or recorded Voice; kinds determine roles. */
2477
2510
  const audioScriptSourceRelationSpec = Object.freeze({
2478
2511
  kind: "audio-script-source",
2479
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio-script"]), new Set([
2480
- "audio",
2481
- "video",
2482
- "voice"
2483
- ])),
2512
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio-script"]), new Set(["audio", "video"])),
2484
2513
  validateMetadata: isEmptyMetadata
2485
2514
  });
2486
2515
  /**
@@ -2507,6 +2536,7 @@ const builtInRelationSpecs = Object.freeze([
2507
2536
  captionAlignmentRelationSpec,
2508
2537
  clipAnchorRelationSpec,
2509
2538
  phoneticScriptRenderRelationSpec,
2539
+ voiceTimbreRelationSpec,
2510
2540
  audioScriptSourceRelationSpec,
2511
2541
  audioScriptMarkerRelationSpec
2512
2542
  ]);
@@ -2536,7 +2566,7 @@ function hasAssetAndSequence(endpoints) {
2536
2566
  return first.entityKind === "asset" && hasSequence(second) || second.entityKind === "asset" && hasSequence(first);
2537
2567
  }
2538
2568
  function isGeneratedMedia(entity) {
2539
- return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio" || entity.entityKind === "voice";
2569
+ return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio";
2540
2570
  }
2541
2571
  function isEmptyMetadata(value) {
2542
2572
  return isJsonObject(value) && Object.keys(value).length === 0;
@@ -2966,7 +2996,7 @@ function resolveGraphLayout(graph) {
2966
2996
  if (clip.entityKind !== "clip" || graph.adjacent(clip.entityId, "track-clip").length !== 1) fail(`Clip ${clip.entityId} must belong to exactly one Track`);
2967
2997
  const marker = graph.one(clip.entityId, "clip-marker", "sequence-marker");
2968
2998
  const content = graph.one(marker.entityId, "marker-content");
2969
- if (!(typedRole === "video_clip" ? ["image", "video"] : typedRole === "speech" ? ["voice"] : typedRole === "caption" ? ["caption"] : ["audio"]).includes(content.entityKind)) fail(`unsupported compatibility content kind ${content.entityKind} on ${typedRole} Track`);
2999
+ if (!(typedRole === "video_clip" ? ["image", "video"] : typedRole === "speech" ? ["audio"] : typedRole === "caption" ? ["caption"] : ["audio"]).includes(content.entityKind)) fail(`unsupported compatibility content kind ${content.entityKind} on ${typedRole} Track`);
2970
3000
  const space = content.payload.coordinateSpace;
2971
3001
  if (space !== "ms" && space !== "milliseconds" && (!object(space) || space.unit !== "ms" && space.unit !== "milliseconds")) fail(`media ${content.entityId} must state millisecond coordinates for this reader`);
2972
3002
  const source = range(marker.payload.sourceRange, "sourceRange");
@@ -3045,7 +3075,7 @@ function resolveGraphLayout(graph) {
3045
3075
  let start = fact.absoluteStart ?? 0;
3046
3076
  if (fact.mode === "anchored") {
3047
3077
  const host = resolve(fact.anchorId);
3048
- if (fact.role === "speech" && !["video_clip", "bgm"].includes(facts.get(host.clipEntityId).role)) fail("An anchored Voice must use a visual or BGM Clip as its host");
3078
+ if (fact.role === "speech" && !["video_clip", "bgm"].includes(facts.get(host.clipEntityId).role)) fail("An anchored voiceover must use a visual or BGM Clip as its host");
3049
3079
  start = host.start + fact.anchorOffset;
3050
3080
  } else if (fact.predecessor !== void 0) {
3051
3081
  const prior = resolve(fact.predecessor);
@@ -3094,7 +3124,7 @@ function resolveGraphLayout(graph) {
3094
3124
  * consulted as an editing fact. Peer IDs appear only in this derived read
3095
3125
  * contract, reconstructed from relations; they are not stored in payloads.
3096
3126
  */
3097
- function projectEntityTimeline(rows, meta) {
3127
+ function projectEntityTimeline(rows) {
3098
3128
  const graph = new EntityProjectionGraph(rows);
3099
3129
  const layout = resolveGraphLayout(graph);
3100
3130
  for (const track of layout.tracks) {
@@ -3129,8 +3159,11 @@ function projectEntityTimeline(rows, meta) {
3129
3159
  const asset = mediaAssetFacts(graph, content);
3130
3160
  requireUntrimmedAudio(content, marker, placement);
3131
3161
  const external = externalLocator(asset, "memota-speech");
3132
- const assembled = graph.adjacent(content.entityId, "phonetic-script-render").length > 0 || content.payload.voice !== void 0 ? assemblePhoneticScriptContent(rows, graph.one(content.entityId, "phonetic-script-render", "phonetic-script").entityId) : void 0;
3133
- const voice = content.payload.voice;
3162
+ const renderEdges = graph.adjacent(content.entityId, "phonetic-script-render");
3163
+ const timbreEdges = graph.adjacent(content.entityId, "voice-timbre");
3164
+ const assembled = renderEdges.length > 0 || timbreEdges.length > 0 ? assemblePhoneticScriptContent(rows, graph.one(content.entityId, "phonetic-script-render", "phonetic-script").entityId) : void 0;
3165
+ if (timbreEdges.length > 1) fail("A voiceover resolves at most one Voice identity");
3166
+ const voice = timbreEdges.length === 0 ? void 0 : graph.one(content.entityId, "voice-timbre", "voice").payload.voice;
3134
3167
  if (voice !== void 0 && (!object(voice) || voice.system !== "voice-library" || typeof voice.key !== "string")) fail("Voice must use an explicit voice-library locator");
3135
3168
  const captionIds = layout.tracks.flatMap((candidate) => candidate.role === "caption" ? candidate.clips.filter((caption) => caption.anchorClipEntityId === clip.entityId).map((caption) => caption.clipEntityId) : []);
3136
3169
  parts[clip.entityId] = { speech: {
@@ -3183,10 +3216,6 @@ function projectEntityTimeline(rows, meta) {
3183
3216
  }
3184
3217
  }
3185
3218
  const document = {
3186
- meta: {
3187
- ...structuredClone(meta),
3188
- schema_version: ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION
3189
- },
3190
3219
  timeline: graph.timeline.payload.unit_time_ms === void 0 ? void 0 : { unit_time_ms: positive(graph.timeline.payload.unit_time_ms, "unit_time_ms") },
3191
3220
  tracks: layout.tracks.map((track) => ({
3192
3221
  id: track.entityId,
@@ -4271,13 +4300,37 @@ function materialize(doc, allowSelectionConflicts = false) {
4271
4300
  for (const [slot, id] of Object.entries(state.project)) if (project.get(slot) !== id) project.set(slot, id);
4272
4301
  }
4273
4302
  //#endregion
4303
+ //#region src/document/document-mutation-guard.ts
4304
+ /**
4305
+ * @internal
4306
+ * Shared by synchronous mutation entry points for one document. The session
4307
+ * closes it before teardown callbacks can attempt another write.
4308
+ */
4309
+ var DocumentMutationGuard = class {
4310
+ running = false;
4311
+ closed = false;
4312
+ run(mutation) {
4313
+ if (this.closed) throw new Error("mengine document is disposed");
4314
+ if (this.running) throw new Error("mengine document mutation is already running");
4315
+ this.running = true;
4316
+ try {
4317
+ return mutation();
4318
+ } finally {
4319
+ this.running = false;
4320
+ }
4321
+ }
4322
+ close() {
4323
+ this.closed = true;
4324
+ }
4325
+ };
4326
+ //#endregion
4274
4327
  //#region src/document/mirror-read.ts
4275
4328
  /**
4276
4329
  * Project the mirror state (`VideoDocumentDraft`) into the authoritative
4277
4330
  * `VideoDocument`. The storage shape is isomorphic to the domain shape (RFC 03
4278
- * §4, reference/17 §4: meta map + a single `tracks` list + part_library), so this
4331
+ * §4, reference/17 §4: timeline + a single `tracks` list + part_library), so this
4279
4332
  * is a near-identity — it reads `time_position` / `fallback_abs_ms` JSON blobs
4280
- * back into structured values and trims empty strings, nothing more.
4333
+ * back into structured values and selects content fields. Legacy meta is ignored.
4281
4334
  *
4282
4335
  * It maps only authoritative facts (RFC 02 §6): `part_id` + `time_position`. The
4283
4336
  * projection-derived `VideoDraft` read-view (absolute time, `part_aggregations`,
@@ -4294,20 +4347,8 @@ function readVideoDocumentFromDraft(draft) {
4294
4347
  if (part != null) partLibrary[partId] = part;
4295
4348
  }
4296
4349
  const timeline = draft.timeline;
4297
- const hasTimeline = timeline?.unit_time_ms != null;
4298
- const meta = draft.meta;
4299
4350
  return {
4300
- meta: {
4301
- schema_version: meta?.schema_version === "video-document/entity-projection-v1" ? ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION : VIDEO_DOCUMENT_SCHEMA_VERSION,
4302
- draft_id: emptyToUndefined(meta?.draft_id),
4303
- project_id: emptyToUndefined(meta?.project_id),
4304
- owner_id: emptyToUndefined(meta?.owner_id),
4305
- thumbnail_storage_key: emptyToUndefined(meta?.thumbnail_storage_key),
4306
- chat_session_id: emptyToUndefined(meta?.chat_session_id),
4307
- video_creation_settings: meta?.video_creation_settings ?? void 0,
4308
- version: meta?.version ?? void 0
4309
- },
4310
- timeline: hasTimeline ? { unit_time_ms: timeline.unit_time_ms } : void 0,
4351
+ timeline: timeline?.unit_time_ms != null ? { unit_time_ms: timeline.unit_time_ms } : void 0,
4311
4352
  tracks: rowsToTracks(draft.tracks),
4312
4353
  part_library: partLibrary
4313
4354
  };
@@ -4331,9 +4372,6 @@ function rowToTrack(row) {
4331
4372
  })
4332
4373
  };
4333
4374
  }
4334
- function emptyToUndefined(value) {
4335
- return value == null || value === "" ? void 0 : value;
4336
- }
4337
4375
  //#endregion
4338
4376
  //#region src/document/mirror-adapter.ts
4339
4377
  /**
@@ -4346,33 +4384,50 @@ function emptyToUndefined(value) {
4346
4384
  * - `transact(edit, audit)` runs the whole op in one `mirror.setState` callback:
4347
4385
  * one diff, one `doc.commit` carrying the audit message. The callback edits an
4348
4386
  * immer draft, so a throw inside it discards the draft and never touches Loro
4349
- * (natural rollback) — no `guard` / `rollback` / `openTransaction` machinery.
4387
+ * (natural rollback) without a document checkout or compensating commit.
4350
4388
  * - mirror's `idSelector` (track items keyed by `part_id`) diffs reorders to
4351
4389
  * real Loro `move` ops on every lane, so per-item CRDT identity survives on
4352
4390
  * main and secondary tracks alike.
4353
4391
  */
4354
4392
  var MirrorVideoDocumentAdapter = class {
4355
4393
  doc;
4394
+ mutationGuard;
4356
4395
  mirror;
4357
- constructor(doc) {
4396
+ disposed = false;
4397
+ constructor(doc, mutationGuard = new DocumentMutationGuard()) {
4358
4398
  this.doc = doc;
4399
+ this.mutationGuard = mutationGuard;
4359
4400
  this.mirror = new Mirror({
4360
4401
  doc,
4361
- schema: videoDocumentMirrorSchema
4402
+ schema: videoDocumentMirrorSchema,
4403
+ validateUpdates: false
4362
4404
  });
4363
4405
  }
4364
4406
  snapshot() {
4365
- const document = readVideoDocumentFromDraft(this.mirror.getState());
4366
- if (this.doc.getMap("medeo").get("schema") === "medeo.entities.loro.v1") return projectEntityTimeline(readEntityDocument(this.doc).rows, document.meta);
4367
- return document;
4407
+ this.assertActive();
4408
+ if (this.isEntityDocument()) return projectEntityTimeline(readEntityDocument(this.doc).rows);
4409
+ return readVideoDocumentFromDraft(this.mirror.getState());
4368
4410
  }
4369
4411
  /**
4370
- * True once the doc holds real document content. A fresh mirror over an empty
4371
- * doc still reports defaulted root maps, so probe the stored `schema_version`
4372
- * (empty until a snapshot is bootstrapped or synced in).
4412
+ * Native Entity authority: placement is already solved by the Entity graph.
4413
+ *
4414
+ * The root check goes through `getShallowValue` first because `getMap` creates
4415
+ * the container on access — probing a legacy document for `medeo` would
4416
+ * otherwise mutate it, which a read must never do.
4373
4417
  */
4374
- hasContent() {
4375
- return (this.mirror.getState().meta?.schema_version ?? "") !== "";
4418
+ isEntityDocument() {
4419
+ const roots = this.doc.getShallowValue();
4420
+ if (!Object.hasOwn(roots, "medeo")) return false;
4421
+ return this.doc.getMap("medeo").get("schema") === LORO_ENTITY_SCHEMA;
4422
+ }
4423
+ /**
4424
+ * Release mirror subscriptions; do not reuse this adapter afterwards.
4425
+ * The caller still owns the borrowed LoroDoc.
4426
+ */
4427
+ dispose() {
4428
+ if (this.disposed) return;
4429
+ this.disposed = true;
4430
+ this.mirror.dispose();
4376
4431
  }
4377
4432
  /**
4378
4433
  * Apply one op as a single transaction. `edit` mutates the immer draft; mirror
@@ -4381,80 +4436,71 @@ var MirrorVideoDocumentAdapter = class {
4381
4436
  * skips the commit — matching the prior "empty op leaves no audit" behavior.
4382
4437
  */
4383
4438
  transact(edit, audit) {
4384
- if (this.snapshot().meta.schema_version === "video-document/entity-projection-v1") throw new Error("Entity timeline projections are read-only; edit the Medeo entities instead");
4385
- this.mirror.setState((draft) => {
4386
- edit(draft);
4387
- }, {
4388
- origin: "mengine.semantic_editor",
4389
- message: JSON.stringify({
4390
- semantic_op: audit.kind,
4391
- payload: audit.payload,
4392
- intent: audit.intent ?? null,
4393
- actor: audit.actor ?? null
4394
- })
4439
+ this.assertActive();
4440
+ if (this.isEntityDocument()) throw new Error("Entity documents are read-only through this adapter; edit the Medeo entities instead");
4441
+ this.mutationGuard.run(() => {
4442
+ this.mirror.setState((draft) => {
4443
+ edit(draft);
4444
+ const result = validateSchema(videoDocumentMirrorSchema, {
4445
+ timeline: draft.timeline,
4446
+ tracks: draft.tracks,
4447
+ part_library: draft.part_library
4448
+ });
4449
+ if (!result.valid) throw new Error(`Invalid content update: ${result.errors?.join("; ")}`);
4450
+ }, {
4451
+ origin: "mengine.semantic_editor",
4452
+ message: JSON.stringify({
4453
+ semantic_op: audit.kind,
4454
+ payload: audit.payload,
4455
+ intent: audit.intent ?? null,
4456
+ actor: audit.actor ?? null
4457
+ })
4458
+ });
4395
4459
  });
4396
4460
  }
4461
+ assertActive() {
4462
+ if (this.disposed) throw new Error("mengine document adapter is disposed");
4463
+ }
4397
4464
  };
4398
4465
  /** Build a fresh Loro doc seeded with `document` through the mirror. */
4399
4466
  function createMirrorVideoDocument(document, options = {}) {
4400
4467
  assertValidVideoDocument(document);
4401
4468
  const doc = new LoroDoc();
4402
- if (options.peerId != null) doc.setPeerId(options.peerId);
4403
- new Mirror({
4404
- doc,
4405
- schema: videoDocumentMirrorSchema
4406
- }).setState((draft) => {
4407
- writeVideoDocumentToDraft(draft, document);
4408
- }, { origin: options.origin ?? "mengine.bootstrap" });
4409
- return doc;
4469
+ try {
4470
+ if (options.peerId != null) doc.setPeerId(options.peerId);
4471
+ const mirror = new Mirror({
4472
+ doc,
4473
+ schema: videoDocumentMirrorSchema
4474
+ });
4475
+ try {
4476
+ mirror.setState((draft) => {
4477
+ seedDraft(draft, document);
4478
+ }, { origin: options.origin ?? "mengine.bootstrap" });
4479
+ return doc;
4480
+ } finally {
4481
+ mirror.dispose();
4482
+ }
4483
+ } catch (error) {
4484
+ doc.free();
4485
+ throw error;
4486
+ }
4410
4487
  }
4411
4488
  /** Build a `MirrorVideoDocumentAdapter` over a fresh doc seeded with `document`. */
4412
4489
  function createMirrorVideoDocumentAdapter(document, options) {
4413
4490
  return new MirrorVideoDocumentAdapter(createMirrorVideoDocument(document, options));
4414
4491
  }
4415
- /** Host-only replacement of a derived view, retaining the existing Loro history. */
4416
- function applyEntityTimelineProjection(doc, projection, entityRevision) {
4417
- if (projection.meta.schema_version !== "video-document/entity-projection-v1") throw new Error("Expected an entity timeline projection");
4418
- if (!Number.isSafeInteger(entityRevision) || entityRevision < 1) throw new Error("Expected a positive entity revision");
4419
- assertValidVideoDocument(projection);
4420
- const from = doc.oplogVersion();
4421
- new Mirror({
4422
- doc,
4423
- schema: videoDocumentMirrorSchema
4424
- }).setState((draft) => writeVideoDocumentToDraft(draft, {
4425
- ...projection,
4426
- meta: {
4427
- ...projection.meta,
4428
- version: entityRevision
4429
- }
4430
- }), {
4431
- origin: "mengine.entity_projection",
4432
- message: JSON.stringify({
4433
- semantic_op: "EntityTimelineProjection",
4434
- entity_revision: entityRevision
4435
- })
4436
- });
4437
- return doc.export({
4438
- mode: "update",
4439
- from
4440
- });
4441
- }
4442
- /** Write a whole `VideoDocument` into a draft (seed a fresh doc / plain-memory state). */
4443
- function writeVideoDocumentToDraft(draft, document) {
4444
- draft.meta = {
4445
- schema_version: document.meta.schema_version,
4446
- draft_id: document.meta.draft_id ?? void 0,
4447
- project_id: document.meta.project_id ?? void 0,
4448
- owner_id: document.meta.owner_id ?? void 0,
4449
- thumbnail_storage_key: document.meta.thumbnail_storage_key ?? void 0,
4450
- chat_session_id: document.meta.chat_session_id ?? void 0,
4451
- video_creation_settings: document.meta.video_creation_settings ?? void 0,
4452
- version: document.meta.version ?? void 0
4453
- };
4492
+ /** Write a whole `VideoDocument` into the mirror draft (seed a fresh doc / plain-memory state). */
4493
+ function seedDraft(draft, document) {
4454
4494
  draft.timeline = { unit_time_ms: document.timeline?.unit_time_ms ?? void 0 };
4455
4495
  draft.tracks = (document.tracks ?? []).map(toTrackRow);
4456
4496
  draft.part_library = {};
4457
- for (const [partId, part] of Object.entries(document.part_library ?? {})) draft.part_library[partId] = partUnionToDraft(part);
4497
+ for (const [partId, part] of Object.entries(document.part_library ?? {})) {
4498
+ const createdPart = part.caption != null ? { caption: {
4499
+ ...part.caption,
4500
+ style: part.caption.style ?? {}
4501
+ } } : part;
4502
+ draft.part_library[partId] = partUnionToDraft(createdPart);
4503
+ }
4458
4504
  }
4459
4505
  /** Map a domain `Track` to a draft track row (no lane/lane_order — see schema). */
4460
4506
  function toTrackRow(track) {
@@ -4538,10 +4584,12 @@ var PlainMemoryAdapter = class {
4538
4584
  * use this so every minted id is appended to the current transact's list.
4539
4585
  */
4540
4586
  idFactory;
4587
+ readOnly;
4541
4588
  constructor(document, options) {
4542
4589
  assertValidVideoDocument(document);
4590
+ this.readOnly = options?.readOnly === true;
4543
4591
  const draft = {};
4544
- writeVideoDocumentToDraft(draft, document);
4592
+ seedDraft(draft, document);
4545
4593
  this.state = draft;
4546
4594
  this.baseIdFactory = options?.idFactory ?? generatePartId;
4547
4595
  this.idFactory = (prefix) => {
@@ -4554,7 +4602,7 @@ var PlainMemoryAdapter = class {
4554
4602
  return this._journal;
4555
4603
  }
4556
4604
  hasContent() {
4557
- return (this.state.meta?.schema_version ?? "") !== "";
4605
+ return this.state.tracks !== void 0 || this.state.timeline !== void 0;
4558
4606
  }
4559
4607
  snapshot() {
4560
4608
  return readVideoDocumentFromDraft(this.state);
@@ -4565,7 +4613,7 @@ var PlainMemoryAdapter = class {
4565
4613
  * no structural change — skip journal, matching mirror "no change, no commit".
4566
4614
  */
4567
4615
  transact(edit, audit) {
4568
- if (this.state.meta?.schema_version === "video-document/entity-projection-v1") throw new Error("Entity timeline projections are read-only; edit the Medeo entities instead");
4616
+ if (this.readOnly) throw new Error("Entity documents are read-only here; edit the Medeo entities instead");
4569
4617
  this.pendingIds = [];
4570
4618
  try {
4571
4619
  const next = produce(this.state, edit);
@@ -4586,4 +4634,4 @@ function createPlainMemoryAdapter(document, options) {
4586
4634
  return new PlainMemoryAdapter(document, options);
4587
4635
  }
4588
4636
  //#endregion
4589
- export { ensureLaneTrack as $, assembleEntityContent as A, hasSequence as B, projectEntityTimeline as C, base64ToBytes as Ct, decodeEntityRelationRows as D, EntityTimelineProjectionError as E, updateEntityFields as F, fromVideoDocument as G, buildInitialVideoDocument as H, variantBaseEntityIds as I, assertValidVideoDocument as J, toVideoDocument as K, isJsonObject as L, assembleScriptText as M, findComposedAudioScript as N, ScriptCompositionError as O, selectAudioScriptSegment as P, LANE_KINDS_IN_STACK_ORDER as Q, createEntityId as R, resolveFieldPath as S, videoDocumentMirrorSchema as St, resolveEntityTimelineLayout as T, buildSpeechHostMap as U, isMediaAssetVariantKind as V, derivePositionFromAbs as W, partUnionSchema as X, validateVideoDocument as Y, videoDocumentSchema as Z, audioScriptAssetHash as _, VIDEO_DOCUMENT_SCHEMA_VERSION as _t, applyEntityTimelineProjection as a, fillMainTrackTimeGaps as at, applyFieldChanges as b, partUnionToDraft as bt, writeVideoDocumentToDraft as c, resolveAllSpeechOverlaps as ct, LoroEntityDocument as d, TIMELINE_SKELETON_DURATION_MS as dt, findLaneTrack as et, LoroEntityDraft as f, isEmptyVideoClip as ft, audioScriptAssetFields as g, ENTITY_TIMELINE_PROJECTION_SCHEMA_VERSION as gt, audioScriptAssetContent as h, DEFAULT_UNIT_TIME_MS as ht, MirrorVideoDocumentAdapter as i, arrangeMainTrackSeamlessly as it, assemblePhoneticScriptContent as j, assembleCaptionContent as k, readVideoDocumentFromDraft as l, resolveSpeechOverlapByShiftingVideos as lt, entityOrderGroups as m, safeDurationMs as mt, createPlainMemoryAdapter as n, solveVideoDocument as nt, createMirrorVideoDocument as o, reassignSpeechesToVideoClipsByTime as ot, readEntityDocument as p, partDurationMs as pt, VideoDocumentValidationError as q, generatePartId as r, cascadeAfterVideoClipChanges as rt, createMirrorVideoDocumentAdapter as s, recalculateTimelineDuration as st, PlainMemoryAdapter as t, laneTrackId as tt, LORO_ENTITY_SCHEMA as u, syncAggregatedClipsTimePosition as ut, hasAudioScriptAsset as v, effectiveVideoClipDurationMs as vt, EntityProjectionGraph as w, bytesToBase64 as wt, assertFieldChanges as x, recordEntries as xt, equalJson as y, speedOf as yt, createRelationId as z };
4637
+ export { findLaneTrack as $, assemblePhoneticScriptContent as A, isMediaAssetVariantKind as B, EntityProjectionGraph as C, ScriptCompositionError as D, decodeEntityRelationRows as E, variantBaseEntityIds as F, toVideoDocument as G, buildSpeechHostMap as H, isJsonObject as I, validateVideoDocument as J, VideoDocumentValidationError as K, createEntityId as L, findComposedAudioScript as M, selectAudioScriptSegment as N, assembleCaptionContent as O, updateEntityFields as P, ensureLaneTrack as Q, createRelationId as R, projectEntityTimeline as S, bytesToBase64 as St, EntityTimelineProjectionError as T, derivePositionFromAbs as U, buildInitialVideoDocument as V, fromVideoDocument as W, videoDocumentSchema as X, partUnionSchema as Y, LANE_KINDS_IN_STACK_ORDER as Z, hasAudioScriptAsset as _, speedOf as _t, createMirrorVideoDocument as a, fillMainTrackTimeGaps as at, assertFieldChanges as b, videoDocumentMirrorSchema as bt, DocumentMutationGuard as c, resolveAllSpeechOverlaps as ct, LoroEntityDraft as d, TIMELINE_SKELETON_DURATION_MS as dt, hasResolvedPlacement as et, readEntityDocument as f, isEmptyVideoClip as ft, audioScriptAssetHash as g, effectiveVideoClipDurationMs as gt, audioScriptAssetFields as h, DEFAULT_UNIT_TIME_MS as ht, MirrorVideoDocumentAdapter as i, arrangeMainTrackSeamlessly as it, assembleScriptText as j, assembleEntityContent as k, LORO_ENTITY_SCHEMA as l, resolveSpeechOverlapByShiftingVideos as lt, audioScriptAssetContent as m, safeDurationMs as mt, createPlainMemoryAdapter as n, solveVideoDocument as nt, createMirrorVideoDocumentAdapter as o, reassignSpeechesToVideoClipsByTime as ot, entityOrderGroups as p, partDurationMs as pt, assertValidVideoDocument as q, generatePartId as r, cascadeAfterVideoClipChanges as rt, readVideoDocumentFromDraft as s, recalculateTimelineDuration as st, PlainMemoryAdapter as t, laneTrackId as tt, LoroEntityDocument as u, syncAggregatedClipsTimePosition as ut, equalJson as v, partUnionToDraft as vt, resolveEntityTimelineLayout as w, resolveFieldPath as x, base64ToBytes as xt, applyFieldChanges as y, recordEntries as yt, hasSequence as z };