@mengine/medeo-client 1.2.1-alpha.5 → 1.2.1-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-ClBRZtLn.js → document-49OdvviW.js} +140 -100
- package/dist/{index-DpD_SncO.d.ts → index-BO2fSUKi.d.ts} +113 -635
- package/dist/index-InEWX9rk.d.ts +609 -0
- package/dist/index.d.ts +40 -36
- package/dist/index.js +165 -683
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +518 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +40 -1
- package/package.json +7 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Mirror, schema } from "loro-mirror";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { LoroDoc } from "loro-crdt";
|
|
4
|
+
import { produce } from "immer";
|
|
4
5
|
//#region src/client/base64.ts
|
|
5
6
|
function bytesToBase64(bytes) {
|
|
6
7
|
let binary = "";
|
|
@@ -87,8 +88,7 @@ function jsonTransform() {
|
|
|
87
88
|
const trackItem = schema.LoroMap({
|
|
88
89
|
part_id: schema.String(),
|
|
89
90
|
time_position: schema.String().transform(jsonTransform()),
|
|
90
|
-
fallback_abs_ms: schema.Number({ required: false })
|
|
91
|
-
duration_override_ms: schema.Number({ required: false })
|
|
91
|
+
fallback_abs_ms: schema.Number({ required: false })
|
|
92
92
|
});
|
|
93
93
|
const track = schema.LoroMap({
|
|
94
94
|
id: schema.String(),
|
|
@@ -111,7 +111,7 @@ const speechPart = schema.LoroMap({
|
|
|
111
111
|
volume: schema.Number(),
|
|
112
112
|
audio_storage_key: schema.String(),
|
|
113
113
|
origin_speech_id: schema.String(),
|
|
114
|
-
voice: schema.String(
|
|
114
|
+
voice: schema.String().transform(jsonTransform()),
|
|
115
115
|
caption_ids: schema.String().transform(jsonTransform())
|
|
116
116
|
}, { required: false });
|
|
117
117
|
const captionStyle = schema.LoroMap({
|
|
@@ -322,71 +322,6 @@ function syncAggregatedClipsTimePosition(doc) {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
/**
|
|
325
|
-
* Resolve authoritative `time_position` edges without inventing parents from
|
|
326
|
-
* geometric overlap. Roots stay absolute/sequential; every anchored descendant
|
|
327
|
-
* follows its declared parent, regardless of whether that parent is video, BGM,
|
|
328
|
-
* or speech.
|
|
329
|
-
*/
|
|
330
|
-
function resolveAnchoredClipsTimePosition(doc) {
|
|
331
|
-
const items = [
|
|
332
|
-
...doc.main_track,
|
|
333
|
-
...doc.bgm_track,
|
|
334
|
-
...doc.speech_track,
|
|
335
|
-
...doc.caption_track
|
|
336
|
-
];
|
|
337
|
-
const byPart = indexByPart(items);
|
|
338
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
339
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
340
|
-
const resolve = (item) => {
|
|
341
|
-
if (resolved.has(item.part_id)) return item.abs_time_position;
|
|
342
|
-
if (visiting.has(item.part_id)) throw new Error(`anchored position cycle at ${item.part_id}`);
|
|
343
|
-
visiting.add(item.part_id);
|
|
344
|
-
const position = item.time_position;
|
|
345
|
-
if (position.mode === "absolute") item.abs_time_position = Math.round(position.offsetMs);
|
|
346
|
-
else if (position.mode === "anchored") {
|
|
347
|
-
const parent = byPart.get(position.anchorPartId);
|
|
348
|
-
if (parent != null) item.abs_time_position = resolve(parent) + Math.round(position.offsetMs);
|
|
349
|
-
}
|
|
350
|
-
visiting.delete(item.part_id);
|
|
351
|
-
resolved.add(item.part_id);
|
|
352
|
-
return item.abs_time_position;
|
|
353
|
-
};
|
|
354
|
-
for (const item of items) resolve(item);
|
|
355
|
-
}
|
|
356
|
-
/** Preserve the historical orphan fallback without re-parenting valid roots. */
|
|
357
|
-
function recoverOrphanedSpeechAnchorsByTime(doc) {
|
|
358
|
-
const placedPartIds = new Set([
|
|
359
|
-
...doc.main_track,
|
|
360
|
-
...doc.bgm_track,
|
|
361
|
-
...doc.speech_track,
|
|
362
|
-
...doc.caption_track
|
|
363
|
-
].map((item) => item.part_id));
|
|
364
|
-
const ranges = doc.main_track.map((item) => ({
|
|
365
|
-
part_id: item.part_id,
|
|
366
|
-
start: item.abs_time_position,
|
|
367
|
-
end: item.abs_time_position + partDurationMs(doc, item.part_id)
|
|
368
|
-
}));
|
|
369
|
-
if (ranges.length === 0) return;
|
|
370
|
-
for (const speech of doc.speech_track) {
|
|
371
|
-
const position = speech.time_position;
|
|
372
|
-
if (position.mode !== "anchored" || placedPartIds.has(position.anchorPartId)) continue;
|
|
373
|
-
const range = ranges.find((candidate) => candidate.start <= speech.abs_time_position && speech.abs_time_position < candidate.end) ?? (speech.abs_time_position < ranges[0].start ? ranges[0] : ranges[ranges.length - 1]);
|
|
374
|
-
const relative = Math.max(0, Math.round(speech.abs_time_position - range.start));
|
|
375
|
-
let aggregation = doc.aggregations.find((candidate) => candidate.body_part_id === range.part_id);
|
|
376
|
-
if (aggregation == null) {
|
|
377
|
-
aggregation = {
|
|
378
|
-
body_part_id: range.part_id,
|
|
379
|
-
attachments: []
|
|
380
|
-
};
|
|
381
|
-
doc.aggregations.push(aggregation);
|
|
382
|
-
}
|
|
383
|
-
aggregation.attachments.push({
|
|
384
|
-
part_id: speech.part_id,
|
|
385
|
-
relative_time_position: relative
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
325
|
* Reassign each speech to the video clip whose time range contains its start,
|
|
391
326
|
* rebuilding `aggregations`. A speech before the first clip or after the last
|
|
392
327
|
* falls back to the first / last clip respectively (FE: see §4 note — FE falls
|
|
@@ -589,14 +524,14 @@ function hostVideoOf(doc, speechPartId) {
|
|
|
589
524
|
//#endregion
|
|
590
525
|
//#region src/timeline-core/entrypoints.ts
|
|
591
526
|
/**
|
|
592
|
-
* The full solve pipeline: arrange →
|
|
593
|
-
*
|
|
527
|
+
* The full solve pipeline: arrange → sync → reassign → resolve-overlap →
|
|
528
|
+
* fill-gaps → recalc. The single cascade the read-side projection runs; ops
|
|
594
529
|
* never call it (they write only facts — RFC 02 §7/§10).
|
|
595
530
|
*/
|
|
596
531
|
function cascadeAfterVideoClipChanges(doc, makeEmptyPart) {
|
|
597
532
|
arrangeMainTrackSeamlessly(doc);
|
|
598
|
-
|
|
599
|
-
|
|
533
|
+
syncAggregatedClipsTimePosition(doc);
|
|
534
|
+
reassignSpeechesToVideoClipsByTime(doc);
|
|
600
535
|
resolveAllSpeechOverlaps(doc);
|
|
601
536
|
fillMainTrackTimeGaps(doc, makeEmptyPart);
|
|
602
537
|
recalculateTimelineDuration(doc);
|
|
@@ -670,7 +605,7 @@ function videoDocumentToTimelineDoc(document) {
|
|
|
670
605
|
caption_track: captionItems,
|
|
671
606
|
bgm_track: bgmItems,
|
|
672
607
|
part_library: partLibrary,
|
|
673
|
-
aggregations: seedAggregations(
|
|
608
|
+
aggregations: seedAggregations(mainItems, speechItems),
|
|
674
609
|
timeline: {
|
|
675
610
|
duration_ms: 0,
|
|
676
611
|
unit_time_ms: document.timeline?.unit_time_ms ?? 0
|
|
@@ -694,18 +629,19 @@ function seedAbs(position, fallbackAbsMs) {
|
|
|
694
629
|
}
|
|
695
630
|
}
|
|
696
631
|
/**
|
|
697
|
-
* Build the
|
|
698
|
-
*
|
|
699
|
-
*
|
|
632
|
+
* Build the cascade `aggregations` parent-map from anchored items: an anchored
|
|
633
|
+
* item's `anchorPartId` is the host's `part_id` (= `body_part_id`), `offsetMs`
|
|
634
|
+
* is `relative_time_position`. Only main-track (video) hosts form aggregations;
|
|
635
|
+
* caption→speech relations are recomputed by the cascade from `caption.start_ms`.
|
|
700
636
|
*/
|
|
701
|
-
function seedAggregations(
|
|
702
|
-
const
|
|
637
|
+
function seedAggregations(mainItems, speechItems) {
|
|
638
|
+
const mainPartIds = new Set(mainItems.map((item) => item.part_id));
|
|
703
639
|
const byHost = /* @__PURE__ */ new Map();
|
|
704
640
|
const order = [];
|
|
705
641
|
for (const speech of speechItems) {
|
|
706
642
|
if (speech.time_position.mode !== "anchored") continue;
|
|
707
643
|
const host = speech.time_position.anchorPartId;
|
|
708
|
-
if (!
|
|
644
|
+
if (!mainPartIds.has(host)) continue;
|
|
709
645
|
let aggregation = byHost.get(host);
|
|
710
646
|
if (aggregation == null) {
|
|
711
647
|
aggregation = {
|
|
@@ -838,8 +774,7 @@ const trackItemTimePositionSchema = z.discriminatedUnion("mode", [
|
|
|
838
774
|
const trackItemSchema = z.object({
|
|
839
775
|
part_id: z.string(),
|
|
840
776
|
time_position: trackItemTimePositionSchema,
|
|
841
|
-
fallback_abs_ms: finiteNumber.optional()
|
|
842
|
-
duration_override_ms: z.number().finite().positive().optional()
|
|
777
|
+
fallback_abs_ms: finiteNumber.optional()
|
|
843
778
|
}).passthrough();
|
|
844
779
|
const trackSchema = z.object({
|
|
845
780
|
id: z.string().optional(),
|
|
@@ -871,7 +806,7 @@ const speechPartSchema = z.object({
|
|
|
871
806
|
volume: finiteNumber,
|
|
872
807
|
audio_storage_key: z.string(),
|
|
873
808
|
origin_speech_id: z.string(),
|
|
874
|
-
voice: z.unknown()
|
|
809
|
+
voice: z.unknown(),
|
|
875
810
|
caption_ids: z.array(z.string())
|
|
876
811
|
}).passthrough();
|
|
877
812
|
const captionPartSchema = z.object({
|
|
@@ -1392,7 +1327,7 @@ function fromVideoDocument(document) {
|
|
|
1392
1327
|
above_main_tracks: aboveTracks.map((t) => draftTrack(t, view.absByPartId)),
|
|
1393
1328
|
below_main_tracks: belowTracks.map((t) => draftTrack(t, view.absByPartId)),
|
|
1394
1329
|
part_aggregations: view.aggregations,
|
|
1395
|
-
part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds
|
|
1330
|
+
part_library: toReadViewPartLibrary(view.partLibrary, view.durationMs, view.derivedFillerPartIds),
|
|
1396
1331
|
version: document.meta.version
|
|
1397
1332
|
};
|
|
1398
1333
|
}
|
|
@@ -1413,7 +1348,7 @@ function fromVideoDocument(document) {
|
|
|
1413
1348
|
* and they are not part of the document (see `fromVideoDocument`). An empty clip a
|
|
1414
1349
|
* writer placed deliberately is NOT in that set and is projected normally.
|
|
1415
1350
|
*/
|
|
1416
|
-
function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds
|
|
1351
|
+
function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds) {
|
|
1417
1352
|
const out = {};
|
|
1418
1353
|
for (const [partId, part] of Object.entries(partLibrary)) {
|
|
1419
1354
|
if (derivedFillerPartIds.has(partId)) continue;
|
|
@@ -1433,7 +1368,7 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds, ca
|
|
|
1433
1368
|
const caption = clone(part.caption);
|
|
1434
1369
|
out[partId] = { caption: {
|
|
1435
1370
|
...caption,
|
|
1436
|
-
duration_ms:
|
|
1371
|
+
duration_ms: caption.initial_duration_ms
|
|
1437
1372
|
} };
|
|
1438
1373
|
} else if (part.bgm != null) out[partId] = { bgm: {
|
|
1439
1374
|
...clone(part.bgm),
|
|
@@ -1442,14 +1377,6 @@ function toReadViewPartLibrary(partLibrary, durationMs, derivedFillerPartIds, ca
|
|
|
1442
1377
|
}
|
|
1443
1378
|
return out;
|
|
1444
1379
|
}
|
|
1445
|
-
function captionDurationOverrides(document) {
|
|
1446
|
-
const result = /* @__PURE__ */ new Map();
|
|
1447
|
-
for (const track of document.tracks ?? []) {
|
|
1448
|
-
if (track.parts_kind !== "caption") continue;
|
|
1449
|
-
for (const item of track.items ?? []) if (item.duration_override_ms != null) result.set(item.part_id, item.duration_override_ms);
|
|
1450
|
-
}
|
|
1451
|
-
return result;
|
|
1452
|
-
}
|
|
1453
1380
|
/** Deep-clone a part payload, dropping the derived read-view `duration_ms`. */
|
|
1454
1381
|
function omitDurationMs(part) {
|
|
1455
1382
|
const { duration_ms: _drop, ...rest } = clone(part);
|
|
@@ -1612,7 +1539,6 @@ function rowToTrack(row) {
|
|
|
1612
1539
|
time_position: item.time_position
|
|
1613
1540
|
};
|
|
1614
1541
|
if (item.fallback_abs_ms != null) result.fallback_abs_ms = item.fallback_abs_ms;
|
|
1615
|
-
if (item.duration_override_ms != null) result.duration_override_ms = item.duration_override_ms;
|
|
1616
1542
|
return result;
|
|
1617
1543
|
})
|
|
1618
1544
|
};
|
|
@@ -1687,7 +1613,7 @@ function createMirrorVideoDocument(document, options = {}) {
|
|
|
1687
1613
|
doc,
|
|
1688
1614
|
schema: videoDocumentMirrorSchema
|
|
1689
1615
|
}).setState((draft) => {
|
|
1690
|
-
|
|
1616
|
+
writeVideoDocumentToDraft(draft, document);
|
|
1691
1617
|
}, { origin: options.origin ?? "mengine.bootstrap" });
|
|
1692
1618
|
return doc;
|
|
1693
1619
|
}
|
|
@@ -1695,8 +1621,8 @@ function createMirrorVideoDocument(document, options = {}) {
|
|
|
1695
1621
|
function createMirrorVideoDocumentAdapter(document, options) {
|
|
1696
1622
|
return new MirrorVideoDocumentAdapter(createMirrorVideoDocument(document, options));
|
|
1697
1623
|
}
|
|
1698
|
-
/** Write a whole `VideoDocument` into
|
|
1699
|
-
function
|
|
1624
|
+
/** Write a whole `VideoDocument` into a draft (seed a fresh doc / plain-memory state). */
|
|
1625
|
+
function writeVideoDocumentToDraft(draft, document) {
|
|
1700
1626
|
draft.meta = {
|
|
1701
1627
|
schema_version: document.meta.schema_version,
|
|
1702
1628
|
draft_id: document.meta.draft_id ?? void 0,
|
|
@@ -1721,10 +1647,124 @@ function toTrackRow(track) {
|
|
|
1721
1647
|
items: (track.items ?? []).map((item) => ({
|
|
1722
1648
|
part_id: item.part_id,
|
|
1723
1649
|
time_position: item.time_position,
|
|
1724
|
-
fallback_abs_ms: item.fallback_abs_ms
|
|
1725
|
-
duration_override_ms: item.duration_override_ms
|
|
1650
|
+
fallback_abs_ms: item.fallback_abs_ms
|
|
1726
1651
|
}))
|
|
1727
1652
|
};
|
|
1728
1653
|
}
|
|
1729
1654
|
//#endregion
|
|
1730
|
-
|
|
1655
|
+
//#region src/editor/id-gen.ts
|
|
1656
|
+
/**
|
|
1657
|
+
* Part-id generation, aligned with the online ecosystem.
|
|
1658
|
+
*
|
|
1659
|
+
* The authoritative online producers — agent-harness (`@harness/shared`
|
|
1660
|
+
* `genObjId`) and director.v2 (`common/obj_id.py` `gen_obj_id`) — both mint part
|
|
1661
|
+
* ids as `` `${prefix}_${ulid()}` ``, and real captured drafts use exactly that
|
|
1662
|
+
* shape (`clip_…` / `spe_…` / `cap_…` / `bgm_…`, each a 26-char ULID). The engine
|
|
1663
|
+
* previously emitted `vc_<base36 timestamp><6 random>`, a different prefix AND a
|
|
1664
|
+
* different encoding — the sole cross-repo id divergence. This module removes it
|
|
1665
|
+
* by emitting the same `<prefix>_<ULID>` bytes.
|
|
1666
|
+
*
|
|
1667
|
+
* The ULID is generated inline (Crockford Base32, 48-bit time + 80-bit random)
|
|
1668
|
+
* rather than pulling the `ulid` npm package: the randomness class matches the
|
|
1669
|
+
* old generator (both `Math.random`-based) and it keeps `@mengine/medeo-client`
|
|
1670
|
+
* dependency-free for a purely mechanical id string. Part ids only need to be
|
|
1671
|
+
* unique and lexicographically time-sortable, which this satisfies.
|
|
1672
|
+
*/
|
|
1673
|
+
/** Crockford Base32 alphabet (no I, L, O, U), per the ULID spec. */
|
|
1674
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
1675
|
+
const TIME_LEN = 10;
|
|
1676
|
+
const RANDOM_LEN = 16;
|
|
1677
|
+
function encodeTime(now) {
|
|
1678
|
+
let out = "";
|
|
1679
|
+
let ms = now;
|
|
1680
|
+
for (let i = TIME_LEN - 1; i >= 0; i--) {
|
|
1681
|
+
const mod = ms % 32;
|
|
1682
|
+
out = CROCKFORD[mod] + out;
|
|
1683
|
+
ms = (ms - mod) / 32;
|
|
1684
|
+
}
|
|
1685
|
+
return out;
|
|
1686
|
+
}
|
|
1687
|
+
function encodeRandom() {
|
|
1688
|
+
let out = "";
|
|
1689
|
+
for (let i = 0; i < RANDOM_LEN; i++) out += CROCKFORD[Math.floor(Math.random() * 32)];
|
|
1690
|
+
return out;
|
|
1691
|
+
}
|
|
1692
|
+
/** A 26-char Crockford Base32 ULID (10-char time + 16-char random). */
|
|
1693
|
+
function ulid() {
|
|
1694
|
+
return encodeTime(Date.now()) + encodeRandom();
|
|
1695
|
+
}
|
|
1696
|
+
function generatePartId(prefix) {
|
|
1697
|
+
return `${prefix}_${ulid()}`;
|
|
1698
|
+
}
|
|
1699
|
+
//#endregion
|
|
1700
|
+
//#region src/document/plain-memory-adapter.ts
|
|
1701
|
+
/**
|
|
1702
|
+
* Pure in-memory `SemanticDocumentAdapter` — no Loro/WASM. Holds a
|
|
1703
|
+
* `VideoDocumentDraft` object and applies each `transact` via immer `produce`,
|
|
1704
|
+
* journaling every audit that actually mutated state (plus any ids minted
|
|
1705
|
+
* during that transact).
|
|
1706
|
+
*
|
|
1707
|
+
* Known benign difference vs `MirrorVideoDocumentAdapter`: the editor's
|
|
1708
|
+
* `setPart` assigns a fresh part object on every call, so a same-value rewrite
|
|
1709
|
+
* produces a new immer state and IS journaled here, while the mirror's deep
|
|
1710
|
+
* diff emits no commit. Terminal `snapshot()` stays equal; replay is idempotent.
|
|
1711
|
+
*/
|
|
1712
|
+
var PlainMemoryAdapter = class {
|
|
1713
|
+
state;
|
|
1714
|
+
_journal = [];
|
|
1715
|
+
baseIdFactory;
|
|
1716
|
+
/** Non-null only while a `transact` edit callback is running. */
|
|
1717
|
+
pendingIds = null;
|
|
1718
|
+
/**
|
|
1719
|
+
* Recording wrapper around the underlying factory. Callers (sandbox editor)
|
|
1720
|
+
* use this so every minted id is appended to the current transact's list.
|
|
1721
|
+
*/
|
|
1722
|
+
idFactory;
|
|
1723
|
+
constructor(document, options) {
|
|
1724
|
+
assertValidVideoDocument(document);
|
|
1725
|
+
const draft = {};
|
|
1726
|
+
writeVideoDocumentToDraft(draft, document);
|
|
1727
|
+
this.state = draft;
|
|
1728
|
+
this.baseIdFactory = options?.idFactory ?? generatePartId;
|
|
1729
|
+
this.idFactory = (prefix) => {
|
|
1730
|
+
const id = this.baseIdFactory(prefix);
|
|
1731
|
+
if (this.pendingIds != null) this.pendingIds.push(id);
|
|
1732
|
+
return id;
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
get journal() {
|
|
1736
|
+
return this._journal;
|
|
1737
|
+
}
|
|
1738
|
+
hasContent() {
|
|
1739
|
+
return (this.state.meta?.schema_version ?? "") !== "";
|
|
1740
|
+
}
|
|
1741
|
+
snapshot() {
|
|
1742
|
+
return readVideoDocumentFromDraft(this.state);
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Apply one op via immer. A throw in `edit` discards the draft (state and
|
|
1746
|
+
* journal unchanged). When `produce` returns the same reference, there was
|
|
1747
|
+
* no structural change — skip journal, matching mirror "no change, no commit".
|
|
1748
|
+
*/
|
|
1749
|
+
transact(edit, audit) {
|
|
1750
|
+
this.pendingIds = [];
|
|
1751
|
+
try {
|
|
1752
|
+
const next = produce(this.state, edit);
|
|
1753
|
+
if (next !== this.state) {
|
|
1754
|
+
this.state = next;
|
|
1755
|
+
this._journal.push({
|
|
1756
|
+
...audit,
|
|
1757
|
+
generated_ids: this.pendingIds
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
} finally {
|
|
1761
|
+
this.pendingIds = null;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
/** Build a `PlainMemoryAdapter` seeded with `document`. */
|
|
1766
|
+
function createPlainMemoryAdapter(document, options) {
|
|
1767
|
+
return new PlainMemoryAdapter(document, options);
|
|
1768
|
+
}
|
|
1769
|
+
//#endregion
|
|
1770
|
+
export { resolveSpeechOverlapByShiftingVideos as A, partUnionToDraft as B, solveVideoDocument as C, reassignSpeechesToVideoClipsByTime as D, fillMainTrackTimeGaps as E, safeDurationMs as F, videoDocumentMirrorSchema as H, DEFAULT_UNIT_TIME_MS as I, VIDEO_DOCUMENT_SCHEMA_VERSION as L, TIMELINE_SKELETON_DURATION_MS as M, isEmptyVideoClip as N, recalculateTimelineDuration as O, partDurationMs as P, effectiveVideoClipDurationMs as R, laneTrackId as S, arrangeMainTrackSeamlessly as T, base64ToBytes as U, recordEntries as V, bytesToBase64 as W, partUnionSchema as _, createMirrorVideoDocument as a, ensureLaneTrack as b, readVideoDocumentFromDraft as c, derivePositionFromAbs as d, fromVideoDocument as f, validateVideoDocument as g, assertValidVideoDocument as h, MirrorVideoDocumentAdapter as i, syncAggregatedClipsTimePosition as j, resolveAllSpeechOverlaps as k, buildInitialVideoDocument as l, VideoDocumentValidationError as m, createPlainMemoryAdapter as n, createMirrorVideoDocumentAdapter as o, toVideoDocument as p, generatePartId as r, writeVideoDocumentToDraft as s, PlainMemoryAdapter as t, buildSpeechHostMap as u, videoDocumentSchema as v, cascadeAfterVideoClipChanges as w, findLaneTrack as x, LANE_KINDS_IN_STACK_ORDER as y, speedOf as z };
|