@mengine/medeo-tool 1.4.1-alpha.2 → 1.4.1-alpha.3

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,138 +1,4 @@
1
- import { SchemaValidator, SemanticEditor, assertCanonicalEditorResources, createEditSandbox, effectiveVideoClipDurationMs, ensureEditorFoundation, fromVideoDocument, importMediaAsset, readDocumentAudioScript, solveVideoDocument, speedOf } from "@mengine/medeo-client";
2
- //#region src/document/compact-projection.ts
3
- const DEFAULT_TEXT_PREVIEW_LENGTH = 24;
4
- /** Kind tag shown in the first column (`video_clip` → `clip`). */
5
- function kindTag(kind) {
6
- return kind === "video_clip" ? "clip" : kind;
7
- }
8
- /** Lane label: `video_clip` tracks display as `main`, otherwise `parts_kind`. */
9
- function laneLabel(partsKind) {
10
- return partsKind === "video_clip" ? "main" : partsKind;
11
- }
12
- /** Speed token: absent → `1`; linear → numeric multiplier; anything else → `custom`. */
13
- function speedToken(speedShift) {
14
- if (speedShift == null) return "1";
15
- if (speedShift.category === "linear") return String(speedOf(speedShift));
16
- return "custom";
17
- }
18
- function effectiveDurationMs(part, timelineDurationMs) {
19
- if (part.video_clip != null) return effectiveVideoClipDurationMs(part.video_clip);
20
- if (part.speech != null) return part.speech.media_duration_ms ?? 0;
21
- if (part.caption != null) return part.caption.initial_duration_ms ?? 0;
22
- if (part.bgm != null) return timelineDurationMs;
23
- return 0;
24
- }
25
- function truncateText(text, budget) {
26
- if (text.length <= budget) return text;
27
- return `${text.slice(0, budget)}…`;
28
- }
29
- function anchorToken(timePosition) {
30
- if (timePosition.mode === "anchored") return `anchor=${timePosition.anchorPartId}+${timePosition.offsetMs}`;
31
- if (timePosition.mode === "absolute") return "anchor=abs";
32
- return "anchor=abs";
33
- }
34
- function clipAttrs(clip) {
35
- const playIn = clip.play_in ?? 0;
36
- const playOut = clip.play_out ?? 0;
37
- return `media=${clip.origin_media_id ?? ""} trim=${playIn}-${playOut} speed=${speedToken(clip.speed_shift)} vol=${clip.volume ?? 0}`;
38
- }
39
- function partAttrs(part, item, textPreviewLength) {
40
- if (part.video_clip != null) return clipAttrs(part.video_clip);
41
- if (part.speech != null) return `${anchorToken(item.time_position)} dur=${part.speech.media_duration_ms ?? 0}`;
42
- if (part.caption != null) {
43
- const preview = truncateText(part.caption.text ?? "", textPreviewLength);
44
- return `${anchorToken(item.time_position)} text="${preview}"`;
45
- }
46
- if (part.bgm != null) return `vol=${part.bgm.volume ?? 0}`;
47
- return "";
48
- }
49
- /**
50
- * Render a `VideoDocument` as compact text: one header line plus one row per
51
- * timeline part (optionally filtered by `onlyPartIds`). Deterministic and
52
- * side-effect free — same document always yields the same string.
53
- */
54
- function renderCompactProjection(document, options) {
55
- const onlyPartIds = options?.onlyPartIds;
56
- const textPreviewLength = options?.textPreviewLength ?? DEFAULT_TEXT_PREVIEW_LENGTH;
57
- const solved = solveVideoDocument(document);
58
- const library = document.part_library ?? {};
59
- const tracks = document.tracks ?? [];
60
- let totalParts = 0;
61
- const rows = [];
62
- for (const track of tracks) {
63
- const partsKind = track.parts_kind;
64
- if (partsKind == null) continue;
65
- const lane = laneLabel(partsKind);
66
- const tag = kindTag(partsKind);
67
- for (const item of track.items ?? []) {
68
- totalParts += 1;
69
- const partId = item.part_id;
70
- if (onlyPartIds != null && !onlyPartIds.has(partId)) continue;
71
- const part = library[partId];
72
- if (part == null) continue;
73
- const abs = solved.absByPartId.get(partId) ?? 0;
74
- const dur = effectiveDurationMs(part, solved.durationMs);
75
- const attrs = partAttrs(part, item, textPreviewLength);
76
- rows.push(`${tag} ${partId} ${lane} [${abs},${abs + dur}) ${attrs}`);
77
- }
78
- }
79
- return [`# draft=${document.meta.draft_id ?? ""} v=${document.meta.version ?? 0} duration=${solved.durationMs} parts=${totalParts} shown=${rows.length}`, ...rows].join("\n");
80
- }
81
- //#endregion
82
- //#region src/sandbox/preview.ts
83
- /**
84
- * Collect part ids referenced by a journal for compact preview filtering.
85
- * Walks known id-shaped payload keys (宁多勿少) and unions `generated_ids`.
86
- */
87
- const PART_ID_KEYS = new Set([
88
- "clip_id",
89
- "clip_ids",
90
- "before_clip_id",
91
- "after_clip_id",
92
- "speech_id",
93
- "speech_ids",
94
- "speech_part_id",
95
- "caption_id",
96
- "caption_ids",
97
- "bgm_id",
98
- "anchor_part_id",
99
- "part_id",
100
- "body_part_id"
101
- ]);
102
- /** Extract every part id a journal entry touches (payload refs + minted ids). */
103
- function collectAffectedPartIds(journal) {
104
- const ids = /* @__PURE__ */ new Set();
105
- for (const entry of journal) {
106
- for (const generated of entry.generated_ids ?? []) if (generated.length > 0) ids.add(generated);
107
- collectFromValue(entry.payload, ids);
108
- }
109
- return ids;
110
- }
111
- function collectFromValue(value, ids) {
112
- if (value == null) return;
113
- if (Array.isArray(value)) {
114
- for (const item of value) collectFromValue(item, ids);
115
- return;
116
- }
117
- if (typeof value !== "object") return;
118
- for (const [key, child] of Object.entries(value)) {
119
- if (PART_ID_KEYS.has(key)) {
120
- if (typeof child === "string" && child.length > 0) ids.add(child);
121
- else if (Array.isArray(child)) {
122
- for (const item of child) if (typeof item === "string" && item.length > 0) ids.add(item);
123
- }
124
- }
125
- collectFromValue(child, ids);
126
- }
127
- }
128
- /**
129
- * Render a ChangePlan preview: header + rows for journal-affected parts only.
130
- * Empty journal → empty `onlyPartIds` (header alone), matching the T2 contract.
131
- */
132
- function renderPreview(document, journal) {
133
- return renderCompactProjection(document, { onlyPartIds: journal.length === 0 ? /* @__PURE__ */ new Set() : collectAffectedPartIds(journal) });
134
- }
135
- //#endregion
1
+ import { applyFieldChanges, assertCanonicalEditorResources, assertFieldChanges, ensureEditorFoundation, importMediaAsset, readDocumentAudioScript } from "@mengine/medeo-client";
136
2
  //#region ../medeo-dsl/src/entities.ts
137
3
  function hasSequence(entity) {
138
4
  if (isKnownNonSequenceKind(entity.entityKind) || isReservedEntityKind(entity.entityKind)) return false;
@@ -1560,12 +1426,17 @@ function businessFacades(entities, relations) {
1560
1426
  create: (input) => {
1561
1427
  if (input.entity_kind === "asset") throw new Error("Asset entities are managed by host assembly");
1562
1428
  assertBusinessPayload(input.payload);
1429
+ if (input.entity_id && entities.get(input.entity_id)) throw new Error(`Entity id ${input.entity_id} already exists`);
1563
1430
  return entities.create(input);
1564
1431
  },
1565
1432
  update: (input) => {
1566
1433
  assertBusiness(input.entity_id);
1567
- assertBusinessPayload(input.payload);
1568
- entities.update(input);
1434
+ if (!Array.isArray(input.changes)) throw new Error("entities.update requires changes");
1435
+ for (const change of input.changes) {
1436
+ if (infrastructureFields.has(change.path?.[0])) throw new Error("Field is managed by host assembly");
1437
+ if (change.path?.[0] === "baseEntityIds" && change.op === "list.insert" && typeof change.value === "string") assertBusiness(change.value);
1438
+ }
1439
+ entities.changeFields(input);
1569
1440
  },
1570
1441
  declareFields: (input) => {
1571
1442
  assertBusiness(input.entity_id);
@@ -1581,9 +1452,7 @@ function businessFacades(entities, relations) {
1581
1452
  }
1582
1453
  for (const relation of incident) relations.unlink({ relation_id: relation.relation_id });
1583
1454
  entities.delete(input);
1584
- },
1585
- readCaptionContent: (input) => entities.readCaptionContent(input),
1586
- readPhoneticScriptContent: (input) => entities.readPhoneticScriptContent(input)
1455
+ }
1587
1456
  },
1588
1457
  relations: {
1589
1458
  list: () => relations.list().filter(visibleRelation),
@@ -1599,14 +1468,11 @@ function businessFacades(entities, relations) {
1599
1468
  if (relation && !visibleRelation(relation)) throw new Error("Asset bindings are managed by host assembly");
1600
1469
  relations.unlink(input);
1601
1470
  },
1602
- linkGenerated: (input) => {
1603
- assertBusiness(input.output_entity_id);
1604
- assertBusiness(input.input_entity_id);
1605
- return relations.linkGenerated(input);
1606
- },
1607
- linkClipAnchor: (input) => relations.linkClipAnchor(input),
1608
- linkPhoneticScriptRender: (input) => relations.linkPhoneticScriptRender(input),
1609
- linkAudioScriptSource: (input) => relations.linkAudioScriptSource(input)
1471
+ update: (input) => {
1472
+ const relation = relations.list().find((row) => row.relation_id === input.relation_id);
1473
+ if (relation && !visibleRelation(relation)) throw new Error("Asset bindings are managed by host assembly");
1474
+ relations.update(input);
1475
+ }
1610
1476
  }
1611
1477
  };
1612
1478
  }
@@ -1688,6 +1554,12 @@ var EntitySandbox = class {
1688
1554
  case "create-entity":
1689
1555
  lines.push(`+ entity ${command.entity.entity_id} kind=${command.entity.entity_kind}`);
1690
1556
  break;
1557
+ case "change-relation":
1558
+ lines.push(`~ relation ${command.relation_id} ${command.changes.map((c) => c.op).join(", ")}`);
1559
+ break;
1560
+ case "change-entity":
1561
+ lines.push(`~ entity ${command.entity_id} ${command.changes.map((c) => c.op).join(", ")}`);
1562
+ break;
1691
1563
  case "update-entity":
1692
1564
  lines.push(`~ entity ${command.entity_id} payload`);
1693
1565
  break;
@@ -1742,6 +1614,7 @@ var EntitySandbox = class {
1742
1614
  });
1743
1615
  },
1744
1616
  create: (input) => this.createEntity(input),
1617
+ changeFields: (input) => this.changeEntity(input),
1745
1618
  update: (input) => {
1746
1619
  assertTrimmed(input.entity_id, "entity_id");
1747
1620
  if (!this.state.entities.some((entity) => entity.entity_id === input.entity_id)) throw new Error(`Entity id "${input.entity_id}" does not exist`);
@@ -1755,12 +1628,28 @@ var EntitySandbox = class {
1755
1628
  const current = this.state.entities.find((entity) => entity.entity_id === input.entity_id);
1756
1629
  if (current === void 0) throw new Error(`Unknown entity "${input.entity_id}"`);
1757
1630
  assembleEntityContent(toDslRows(this.state), createEntityId(input.entity_id));
1631
+ const payload = {
1632
+ ...current.payload,
1633
+ ...input.payload
1634
+ };
1635
+ const rows = toDslRows({
1636
+ ...this.state,
1637
+ entities: this.state.entities.map((row) => row.entity_id === input.entity_id ? {
1638
+ ...row,
1639
+ payload
1640
+ } : row)
1641
+ });
1642
+ for (const row of rows.entities) assembleEntityContent(rows, row.entityId);
1643
+ const assembled = assembleEntityContent(rows, createEntityId(input.entity_id));
1644
+ const issues = validateEntity(createEntityRef({
1645
+ ...assembled.payload,
1646
+ entityId: assembled.entityId,
1647
+ entityKind: assembled.entityKind
1648
+ }));
1649
+ if (issues.length) throw new Error(issues.map((issue) => issue.message).join("; "));
1758
1650
  this.replaceOwnedPayload({
1759
1651
  entity_id: input.entity_id,
1760
- payload: {
1761
- ...current.payload,
1762
- ...input.payload
1763
- }
1652
+ payload
1764
1653
  });
1765
1654
  },
1766
1655
  delete: (input) => this.deleteEntity(input),
@@ -1780,7 +1669,8 @@ var EntitySandbox = class {
1780
1669
  linkClipAnchor: (input) => this.linkClipAnchor(input),
1781
1670
  linkPhoneticScriptRender: (input) => this.linkPhoneticScriptRender(input),
1782
1671
  linkAudioScriptSource: (input) => this.linkAudioScriptSource(input),
1783
- unlink: (input) => this.unlinkRelation(input)
1672
+ unlink: (input) => this.unlinkRelation(input),
1673
+ update: (input) => this.changeRelation(input)
1784
1674
  };
1785
1675
  }
1786
1676
  createEntity(input) {
@@ -1818,6 +1708,10 @@ var EntitySandbox = class {
1818
1708
  entityId: createEntityId(entityId),
1819
1709
  entityKind: entity.entity_kind
1820
1710
  });
1711
+ assembleEntityContent(toDslRows({
1712
+ ...this.state,
1713
+ entities: [...this.state.entities, entity]
1714
+ }), createEntityId(entityId));
1821
1715
  this.record({
1822
1716
  kind: "create-entity",
1823
1717
  entity
@@ -1848,6 +1742,22 @@ var EntitySandbox = class {
1848
1742
  payload
1849
1743
  });
1850
1744
  }
1745
+ changeEntity(input) {
1746
+ assertFieldChanges(input.changes);
1747
+ this.record({
1748
+ kind: "change-entity",
1749
+ entity_id: input.entity_id,
1750
+ changes: clone(input.changes)
1751
+ });
1752
+ }
1753
+ changeRelation(input) {
1754
+ assertFieldChanges(input.changes);
1755
+ this.record({
1756
+ kind: "change-relation",
1757
+ relation_id: input.relation_id,
1758
+ changes: clone(input.changes)
1759
+ });
1760
+ }
1851
1761
  deleteEntity(input) {
1852
1762
  assertTrimmed(input.entity_id, "entity_id");
1853
1763
  this.record({
@@ -1893,12 +1803,11 @@ var EntitySandbox = class {
1893
1803
  }
1894
1804
  }
1895
1805
  link(input) {
1896
- if (input.relation_kind === "generated") throw new Error("Author generated Relations with relations.linkGenerated({ output_entity_id, input_entity_id })");
1897
1806
  const spec = builtInRelationSpecs.find((candidate) => candidate.kind === input.relation_kind);
1898
1807
  if (spec == null) throw new Error(`Unknown Relation kind "${String(input.relation_kind)}"`);
1899
1808
  const relation = this.relationFromInput(input, input.relation_kind);
1900
1809
  const [first, second] = this.refsFor(relation);
1901
- new BiRelationIndex().link({
1810
+ new BiRelationIndex().linkRuntime({
1902
1811
  relationId: createRelationId(relation.relation_id),
1903
1812
  spec,
1904
1813
  endpoints: [first, second],
@@ -2039,7 +1948,7 @@ var EntitySandbox = class {
2039
1948
  if (command.entity.entity_kind === "audio-script" && this.state.entities.some((entity) => entity.entity_kind === "audio-script")) throw new Error("The project AudioScript already exists; edit its segments instead");
2040
1949
  if (enforceIdentity && this.state.entities.some((entity) => entity.entity_id === command.entity.entity_id)) throw new Error(`Entity id "${command.entity.entity_id}" already exists`);
2041
1950
  const original = this.original.entities.find((entity) => entity.entity_id === command.entity.entity_id);
2042
- if (enforceIdentity && original != null && original.entity_kind !== command.entity.entity_kind) throw new Error(`Entity id "${command.entity.entity_id}" was originally kind "${original.entity_kind}" and cannot be recreated as "${command.entity.entity_kind}"`);
1951
+ if (enforceIdentity && original != null) throw new Error(`Entity id "${command.entity.entity_id}" was originally kind "${original.entity_kind}" and cannot be recreated as "${command.entity.entity_kind}"`);
2043
1952
  this.state.entities.push(clone(command.entity));
2044
1953
  if (command.entity.entity_kind === "audio-script" && this.state.audioScriptEntityId === null) this.state.audioScriptEntityId = command.entity.entity_id;
2045
1954
  return;
@@ -2055,8 +1964,66 @@ var EntitySandbox = class {
2055
1964
  };
2056
1965
  return;
2057
1966
  }
1967
+ case "change-entity": {
1968
+ const original = this.state;
1969
+ this.state = cloneSnapshot(original);
1970
+ try {
1971
+ const touched = new Set([command.entity_id]);
1972
+ for (const change of command.changes) {
1973
+ if (change.path[0] === "baseEntityIds" && change.op === "list.move") throw new Error("Composition bases are unordered; list.move is not applicable");
1974
+ const owner = resolveEntityFieldOwner(toDslRows(this.state), createEntityId(command.entity_id), change.path[0]);
1975
+ const row = this.state.entities.find((row) => row.entity_id === owner);
1976
+ row.payload = applyFieldChanges(row.payload, [change]);
1977
+ touched.add(owner);
1978
+ }
1979
+ const rows = toDslRows(this.state);
1980
+ for (const row of rows.entities) {
1981
+ const assembled = assembleEntityContent(rows, row.entityId);
1982
+ if (touched.has(row.entityId)) {
1983
+ const issues = validateEntity(createEntityRef({
1984
+ ...assembled.payload,
1985
+ entityId: row.entityId,
1986
+ entityKind: row.entityKind
1987
+ }));
1988
+ if (issues.length) throw new Error(issues.map((issue) => issue.message).join("; "));
1989
+ }
1990
+ }
1991
+ } catch (error) {
1992
+ this.state = original;
1993
+ throw error;
1994
+ }
1995
+ return;
1996
+ }
1997
+ case "change-relation": {
1998
+ const index = this.state.relations.findIndex((row) => row.relation_id === command.relation_id);
1999
+ if (index < 0) throw new Error(`Unknown relation ${command.relation_id}`);
2000
+ for (const change of command.changes) if (!["metadata", "trace"].includes(change.path[0]) || change.path.length < 2) throw new Error("Relation changes may only edit metadata or trace fields");
2001
+ const current = this.state.relations[index];
2002
+ const fields = applyFieldChanges({
2003
+ metadata: current.metadata,
2004
+ trace: current.trace
2005
+ }, command.changes);
2006
+ const next = {
2007
+ ...current,
2008
+ metadata: fields.metadata,
2009
+ trace: fields.trace
2010
+ };
2011
+ const spec = builtInRelationSpecs.find((spec) => spec.kind === next.relation_kind);
2012
+ new BiRelationIndex().linkRuntime({
2013
+ relationId: createRelationId(next.relation_id),
2014
+ spec,
2015
+ endpoints: this.refsFor(next),
2016
+ metadata: next.metadata,
2017
+ trace: next.trace
2018
+ });
2019
+ this.state.relations[index] = next;
2020
+ return;
2021
+ }
2058
2022
  case "delete-entity": {
2059
- if (this.state.entities.find((entity) => entity.entity_id === command.entity_id)?.entity_kind === "audio-script") throw new Error("The document AudioScript is a fixed project identity and cannot be deleted");
2023
+ const target = this.state.entities.find((entity) => entity.entity_id === command.entity_id);
2024
+ if (target && ["audio-script", "timeline"].includes(target.entity_kind)) throw new Error("The document AudioScript is a fixed project identity and cannot be deleted");
2025
+ const dependents = this.state.entities.filter((entity) => Array.isArray(entity.payload.baseEntityIds) && entity.payload.baseEntityIds.includes(command.entity_id));
2026
+ if (dependents.length) throw new Error(`Entity ${command.entity_id} is still referenced by variants: ${dependents.map((row) => row.entity_id).join(", ")}`);
2060
2027
  const index = this.state.entities.findIndex((entity) => entity.entity_id === command.entity_id);
2061
2028
  if (index < 0) throw new Error(`Entity id "${command.entity_id}" does not exist`);
2062
2029
  const incidentRelationIds = this.state.relations.filter((relation) => relation.endpoint_0_entity_id === command.entity_id || relation.endpoint_1_entity_id === command.entity_id).map((relation) => relation.relation_id).sort();
@@ -2130,350 +2097,6 @@ const numericMarkerComparators = { compareMarkerPoints: (_marker, _range, left,
2130
2097
  return left - right;
2131
2098
  } };
2132
2099
  //#endregion
2133
- //#region src/sandbox/script-session.ts
2134
- const LOG_LINE_MAX = 2e3;
2135
- const LOG_LINE_CAP = 1e3;
2136
- const LOG_BYTE_CAP = 64 * 1024;
2137
- const TRUNCATE_MARK = "[truncated]";
2138
- const LOG_TRUNCATED = "[log truncated]";
2139
- const ENTITY_CHECKPOINT_INDEX = Symbol("entityCheckpointIndex");
2140
- /** Session core for one forked document; globals stay identity-stable across rollback. */
2141
- var EditSandboxSession = class {
2142
- original;
2143
- idFactory;
2144
- onEntry;
2145
- onLog;
2146
- onTruncate;
2147
- entitySandbox;
2148
- current;
2149
- /** Adapter journal length already accounted for — new slices are real commits. */
2150
- adapterJournalSeen = 0;
2151
- entries = [];
2152
- logs = [];
2153
- logBytes = 0;
2154
- logCapped = false;
2155
- edit;
2156
- timeline;
2157
- entities;
2158
- relations;
2159
- console;
2160
- checkpoint;
2161
- rollbackTo;
2162
- constructor(document, options) {
2163
- this.original = structuredClone(document);
2164
- this.idFactory = options?.idFactory;
2165
- this.onEntry = options?.onEntry;
2166
- this.onLog = options?.onLog;
2167
- this.onTruncate = options?.onTruncate;
2168
- this.entitySandbox = new EntitySandbox({
2169
- state: options?.entityState,
2170
- idFactory: options?.domainIdFactory ?? (() => {
2171
- throw new Error("Entity id factory is unavailable in this sandbox host");
2172
- }),
2173
- onCommand: options?.onEntityCommand,
2174
- onTruncate: options?.onEntityTruncate
2175
- });
2176
- this.current = this.boot(structuredClone(this.original));
2177
- this.adapterJournalSeen = this.current.adapter.journal.length;
2178
- this.edit = this.buildEditFacade();
2179
- this.timeline = this.buildTimelineFacade();
2180
- this.entities = this.entitySandbox.entities;
2181
- this.relations = this.entitySandbox.relations;
2182
- this.console = this.buildConsoleShim();
2183
- this.checkpoint = () => {
2184
- const checkpoint = { index: this.entries.length };
2185
- Object.defineProperty(checkpoint, ENTITY_CHECKPOINT_INDEX, {
2186
- value: this.entitySandbox.commandCount,
2187
- enumerable: false
2188
- });
2189
- return checkpoint;
2190
- };
2191
- this.rollbackTo = (cp) => this.doRollbackTo(cp);
2192
- }
2193
- /** Assemble a ChangePlan from the self-maintained journal + current preview. */
2194
- buildPlan(baseVersion) {
2195
- const entityCommands = this.entitySandbox.getCommands();
2196
- if (this.entries.length > 0 && entityCommands.length > 0) throw new Error("A sandbox plan cannot mix timeline and entity mutations; run and commit them as separate plans");
2197
- const entityPlan = this.entitySandbox.buildPlan();
2198
- const planKind = entityCommands.length > 0 ? "entities" : "timeline";
2199
- return {
2200
- plan_kind: planKind,
2201
- doc_id: this.original.meta.draft_id ?? "",
2202
- base_version: baseVersion,
2203
- ops: this.entries.slice(),
2204
- entity_base_revision: entityPlan.base_revision,
2205
- entity_commands: entityPlan.commands,
2206
- ...planKind === "entities" ? { entity_rows: entityPlan.rows } : {},
2207
- ...planKind === "entities" ? {
2208
- deleted_entity_ids: entityPlan.deleted_entity_ids,
2209
- deleted_relation_ids: entityPlan.deleted_relation_ids
2210
- } : {},
2211
- preview: planKind === "entities" ? this.entitySandbox.renderPreview() : renderPreview(this.current.adapter.snapshot(), this.entries),
2212
- logs: this.logs.slice()
2213
- };
2214
- }
2215
- getEntries() {
2216
- return this.entries;
2217
- }
2218
- getLogs() {
2219
- return this.logs;
2220
- }
2221
- boot(document) {
2222
- const sandbox = createEditSandbox(document, this.idFactory != null ? { idFactory: this.idFactory } : void 0);
2223
- return {
2224
- adapter: sandbox.adapter,
2225
- editor: sandbox.editor
2226
- };
2227
- }
2228
- doRollbackTo(cp) {
2229
- if (cp.index > this.entries.length) throw new Error(`rollbackTo: checkpoint index ${cp.index} is past journal length ${this.entries.length}`);
2230
- const prefix = this.entries.slice(0, cp.index);
2231
- const next = this.boot(structuredClone(this.original));
2232
- replayJournalSync(next.adapter, prefix);
2233
- this.entries.length = 0;
2234
- this.entries.push(...prefix);
2235
- this.current = next;
2236
- this.adapterJournalSeen = next.adapter.journal.length;
2237
- this.onTruncate?.(prefix.length);
2238
- const entityIndex = cp[ENTITY_CHECKPOINT_INDEX];
2239
- if (entityIndex !== void 0) this.entitySandbox.rollbackTo(entityIndex);
2240
- }
2241
- captureNewEntries() {
2242
- const journal = this.current.adapter.journal;
2243
- if (journal.length <= this.adapterJournalSeen) return;
2244
- const fresh = journal.slice(this.adapterJournalSeen);
2245
- this.adapterJournalSeen = journal.length;
2246
- for (const entry of fresh) {
2247
- this.entries.push(entry);
2248
- this.onEntry?.(entry);
2249
- }
2250
- }
2251
- appendLog(line) {
2252
- if (this.logCapped) return;
2253
- if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {
2254
- this.logs.push(LOG_TRUNCATED);
2255
- this.logCapped = true;
2256
- this.onLog?.(LOG_TRUNCATED);
2257
- return;
2258
- }
2259
- let out = line;
2260
- if (out.length > LOG_LINE_MAX) out = `${out.slice(0, LOG_LINE_MAX - 11)}${TRUNCATE_MARK}`;
2261
- this.logs.push(out);
2262
- this.logBytes += out.length;
2263
- this.onLog?.(out);
2264
- }
2265
- buildConsoleShim() {
2266
- const write = (...args) => {
2267
- this.appendLog(args.map(formatLogArg).join(" "));
2268
- };
2269
- return {
2270
- log: write,
2271
- info: write,
2272
- warn: write,
2273
- error: write
2274
- };
2275
- }
2276
- buildEditFacade() {
2277
- const wrap = (method) => async (input) => {
2278
- await method(this.current.editor, input);
2279
- this.captureNewEntries();
2280
- };
2281
- return {
2282
- addSpeeches: wrap((e, i) => e.addSpeeches(i)),
2283
- addVideoClips: async (input) => {
2284
- const needsAppend = input.before_clip_id == null && input.after_clip_id == null && input.clips.some((clip) => clip.start_ms == null);
2285
- const appendAt = this.timeline.snapshot().timeline?.duration_ms ?? 0;
2286
- const normalized = needsAppend ? {
2287
- ...input,
2288
- clips: input.clips.map((clip) => clip.start_ms == null ? {
2289
- ...clip,
2290
- start_ms: appendAt
2291
- } : clip)
2292
- } : input;
2293
- await this.current.editor.addVideoClips(normalized);
2294
- this.captureNewEntries();
2295
- },
2296
- adjustBgmVolume: wrap((e, i) => e.adjustBgmVolume(i)),
2297
- adjustSpeechVolume: wrap((e, i) => e.adjustSpeechVolume(i)),
2298
- adjustVideoClipDuration: wrap((e, i) => e.adjustVideoClipDuration(i)),
2299
- adjustVideoClipVolume: wrap((e, i) => e.adjustVideoClipVolume(i)),
2300
- changeSpeechScript: wrap((e, i) => e.changeSpeechScript(i)),
2301
- changeSpeechVoice: wrap((e, i) => e.changeSpeechVoice(i)),
2302
- deleteBgm: wrap((e, i) => e.deleteBgm(i)),
2303
- deleteSpeeches: wrap((e, i) => e.deleteSpeeches(i)),
2304
- deleteVideoClips: wrap((e, i) => e.deleteVideoClips(i)),
2305
- moveSpeeches: wrap((e, i) => e.moveSpeeches(i)),
2306
- moveVideoClips: wrap((e, i) => e.moveVideoClips(i)),
2307
- moveVideoClipsByAnchor: wrap((e, i) => e.moveVideoClipsByAnchor(i)),
2308
- replaceVideoClipContent: wrap((e, i) => e.replaceVideoClipContent(i)),
2309
- replaceVideoClipSequence: wrap((e, i) => e.replaceVideoClipSequence(i)),
2310
- setBgm: wrap((e, i) => e.setBgm(i)),
2311
- setCaptionStyle: wrap((e, i) => e.setCaptionStyle(i)),
2312
- setCaptionVisibility: wrap((e, i) => e.setCaptionVisibility(i)),
2313
- setVideoClipSpeedShift: wrap((e, i) => e.setVideoClipSpeedShift(i))
2314
- };
2315
- }
2316
- buildTimelineFacade() {
2317
- return {
2318
- snapshot: () => fromVideoDocument(this.current.adapter.snapshot()),
2319
- clipsInRange: (startMs, endMs) => this.clipsInRange(startMs, endMs),
2320
- part: (id) => this.part(id)
2321
- };
2322
- }
2323
- clipsInRange(startMs, endMs) {
2324
- const document = this.current.adapter.snapshot();
2325
- const solved = solveVideoDocument(document);
2326
- const library = document.part_library ?? {};
2327
- const main = document.tracks?.find((track) => track.parts_kind === "video_clip");
2328
- const out = [];
2329
- for (const item of main?.items ?? []) {
2330
- const id = item.part_id;
2331
- if (id == null) continue;
2332
- const clip = library[id]?.video_clip;
2333
- if (clip == null) continue;
2334
- const start = solved.absByPartId.get(id) ?? 0;
2335
- const duration = effectiveVideoClipDurationMs(clip);
2336
- const end = start + duration;
2337
- const mid = start + duration / 2;
2338
- if (!(mid >= startMs && mid < endMs)) continue;
2339
- out.push({
2340
- id,
2341
- start_ms: start,
2342
- end_ms: end,
2343
- duration_ms: duration,
2344
- speed_shift: clip.speed_shift,
2345
- volume: clip.volume,
2346
- media_id: clip.origin_media_id
2347
- });
2348
- }
2349
- return out;
2350
- }
2351
- part(id) {
2352
- const document = this.current.adapter.snapshot();
2353
- const part = (document.part_library ?? {})[id];
2354
- if (part == null) return null;
2355
- let lane = "main";
2356
- let kind = "video_clip";
2357
- for (const track of document.tracks ?? []) {
2358
- if (!(track.items ?? []).some((item) => item.part_id === id)) continue;
2359
- const partsKind = track.parts_kind ?? "video_clip";
2360
- kind = partsKind;
2361
- lane = partsKind === "video_clip" ? "main" : partsKind;
2362
- break;
2363
- }
2364
- const solved = solveVideoDocument(document);
2365
- const start = solved.absByPartId.get(id) ?? 0;
2366
- let duration = 0;
2367
- if (part.video_clip != null) duration = effectiveVideoClipDurationMs(part.video_clip);
2368
- else if (part.speech != null) duration = part.speech.media_duration_ms ?? 0;
2369
- else if (part.caption != null) duration = part.caption.initial_duration_ms ?? 0;
2370
- else if (part.bgm != null) duration = solved.durationMs;
2371
- return {
2372
- id,
2373
- kind,
2374
- lane,
2375
- start_ms: start,
2376
- end_ms: start + duration,
2377
- duration_ms: duration,
2378
- part
2379
- };
2380
- }
2381
- };
2382
- function formatLogArg(value) {
2383
- if (typeof value === "string") return value;
2384
- if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return String(value);
2385
- try {
2386
- return JSON.stringify(value);
2387
- } catch {
2388
- return "[unstringifiable]";
2389
- }
2390
- }
2391
- /**
2392
- * Synchronous journal replay for rollback. Editor methods are `async` only for
2393
- * interface uniformity — their bodies complete before the Promise is returned,
2394
- * so voiding the call applies mutations in-order without yielding.
2395
- */
2396
- function replayJournalSync(adapter, journal) {
2397
- const queue = [];
2398
- const idFactory = (_prefix) => {
2399
- const id = queue.shift();
2400
- if (id == null) throw new Error("unrecorded id");
2401
- return id;
2402
- };
2403
- const editor = new SemanticEditor(adapter, new SchemaValidator(), idFactory);
2404
- for (const entry of journal) {
2405
- queue.push(...entry.generated_ids ?? []);
2406
- const payload = entry.payload;
2407
- switch (entry.kind) {
2408
- case "MoveVideoClips":
2409
- editor.moveVideoClips(payload);
2410
- break;
2411
- case "MoveVideoClipsByAnchor":
2412
- editor.moveVideoClipsByAnchor(payload);
2413
- break;
2414
- case "DeleteVideoClips":
2415
- editor.deleteVideoClips(payload);
2416
- break;
2417
- case "AddVideoClips":
2418
- editor.addVideoClips(payload);
2419
- break;
2420
- case "AdjustVideoClipVolume":
2421
- editor.adjustVideoClipVolume(payload);
2422
- break;
2423
- case "SetVideoClipSpeedShift":
2424
- editor.setVideoClipSpeedShift(payload);
2425
- break;
2426
- case "ReplaceVideoClipContent":
2427
- editor.replaceVideoClipContent(payload);
2428
- break;
2429
- case "ReplaceVideoClipSequence":
2430
- editor.replaceVideoClipSequence(payload);
2431
- break;
2432
- case "AdjustVideoClipDuration":
2433
- editor.adjustVideoClipDuration(payload);
2434
- break;
2435
- case "AddSpeeches":
2436
- editor.addSpeeches(payload);
2437
- break;
2438
- case "DeleteSpeeches":
2439
- editor.deleteSpeeches(payload);
2440
- break;
2441
- case "MoveSpeeches":
2442
- editor.moveSpeeches(payload);
2443
- break;
2444
- case "ChangeSpeechScript":
2445
- editor.changeSpeechScript(payload);
2446
- break;
2447
- case "ChangeSpeechVoice":
2448
- editor.changeSpeechVoice(payload);
2449
- break;
2450
- case "AdjustSpeechVolume":
2451
- editor.adjustSpeechVolume(payload);
2452
- break;
2453
- case "SetCaptionVisibility":
2454
- editor.setCaptionVisibility(payload);
2455
- break;
2456
- case "SetCaptionStyle":
2457
- editor.setCaptionStyle(payload);
2458
- break;
2459
- case "SetBgm":
2460
- editor.setBgm(payload);
2461
- break;
2462
- case "DeleteBgm":
2463
- editor.deleteBgm(payload);
2464
- break;
2465
- case "AdjustBgmVolume":
2466
- editor.adjustBgmVolume(payload);
2467
- break;
2468
- default: {
2469
- const _exhaustive = entry.kind;
2470
- throw new Error(`replayJournalSync: unsupported kind ${String(_exhaustive)}`);
2471
- }
2472
- }
2473
- if (queue.length > 0) throw new Error("unconsumed ids");
2474
- }
2475
- }
2476
- //#endregion
2477
- export { createEntityId as a, collectAffectedPartIds as c, businessState as i, renderPreview as l, EntitySandbox as n, createRelationId as o, businessFacades as r, isMediaAssetVariantKind as s, EditSandboxSession as t, renderCompactProjection as u };
2100
+ export { createEntityId as a, businessState as i, toDslRows as n, createRelationId as o, businessFacades as r, isMediaAssetVariantKind as s, EntitySandbox as t };
2478
2101
 
2479
- //# sourceMappingURL=script-session-lXpqmupK.mjs.map
2102
+ //# sourceMappingURL=entity-sandbox-BH-7F5C8.mjs.map