@mengine/medeo-tool 1.4.1-alpha.5 → 2.0.1-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{entity-contract-DlmUSouB.d.mts → entity-contract-Cf3AiSe7.d.mts} +45 -43
- package/dist/{entity-sandbox-TaUVT3on.mjs → entity-sandbox-BTR2cRl1.mjs} +255 -139
- package/dist/entity-sandbox-BTR2cRl1.mjs.map +1 -0
- package/dist/index.d.mts +27 -10
- package/dist/index.mjs +104 -116
- package/dist/index.mjs.map +1 -1
- package/dist/sandbox-api.d.mts +42 -44
- package/dist/worker-entry.d.mts +2 -1
- package/dist/worker-entry.mjs +101 -108
- package/dist/worker-entry.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/entity-sandbox-TaUVT3on.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as UpdateEntityInput, S as UnlinkRelationInput, _ as LinkRelationInput, a as DeleteEntityInput, b as SandboxEntity, c as EntitySandboxCheckpoint, d as JsonObject, f as JsonPrimitive, g as LinkGeneratedRelationInput, h as KnownRelationKind, i as CreateEntityInput, l as EntityStoreSnapshot, m as KnownEntityKind, n as BusinessEntityFacade, o as EntityCommand, p as JsonValue, r as BusinessRelationFacade, s as EntityPlanState, t as AuthorableRelationKind, u as EntityUpdateInput, v as RelationUpdateInput, x as SandboxRelation, y as ResourceEntityKind } from "./entity-contract-
|
|
1
|
+
import { C as UpdateEntityInput, S as UnlinkRelationInput, _ as LinkRelationInput, a as DeleteEntityInput, b as SandboxEntity, c as EntitySandboxCheckpoint, d as JsonObject, f as JsonPrimitive, g as LinkGeneratedRelationInput, h as KnownRelationKind, i as CreateEntityInput, l as EntityStoreSnapshot, m as KnownEntityKind, n as BusinessEntityFacade, o as EntityCommand, p as JsonValue, r as BusinessRelationFacade, s as EntityPlanState, t as AuthorableRelationKind, u as EntityUpdateInput, v as RelationUpdateInput, x as SandboxRelation, y as ResourceEntityKind } from "./entity-contract-Cf3AiSe7.mjs";
|
|
2
2
|
import { JournalEntry, ManualSyncDoc, MediaAssetFact, PartIdFactory, SemanticOpName, VideoDocument, VideoDraft } from "@mengine/medeo-client";
|
|
3
3
|
|
|
4
4
|
//#region src/document/compact-projection.d.ts
|
|
@@ -11,6 +11,14 @@ import { JournalEntry, ManualSyncDoc, MediaAssetFact, PartIdFactory, SemanticOpN
|
|
|
11
11
|
* `fromVideoDocument`.
|
|
12
12
|
*/
|
|
13
13
|
interface CompactProjectionOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Document identity for the header. Stated by the caller: the document
|
|
16
|
+
* content no longer carries its own id or revision (business metadata left
|
|
17
|
+
* the CRDT), and the model needs to know which draft this summary describes.
|
|
18
|
+
* Omitted values keep the header's fixed shape with the historical blanks.
|
|
19
|
+
*/
|
|
20
|
+
docId?: string;
|
|
21
|
+
revision?: number;
|
|
14
22
|
/** Only render these parts (header still reports the full timeline total). Default = all. */
|
|
15
23
|
onlyPartIds?: ReadonlySet<string>;
|
|
16
24
|
/** Caption text preview truncation length. Default 24. */
|
|
@@ -30,7 +38,10 @@ declare function collectAffectedPartIds(journal: readonly JournalEntry[]): Set<s
|
|
|
30
38
|
* Render a ChangePlan preview: header + rows for journal-affected parts only.
|
|
31
39
|
* Empty journal → empty `onlyPartIds` (header alone), matching the T2 contract.
|
|
32
40
|
*/
|
|
33
|
-
declare function renderPreview(document: VideoDocument, journal: readonly JournalEntry[]
|
|
41
|
+
declare function renderPreview(document: VideoDocument, journal: readonly JournalEntry[], identity?: {
|
|
42
|
+
docId?: string;
|
|
43
|
+
revision?: number;
|
|
44
|
+
}): string;
|
|
34
45
|
//#endregion
|
|
35
46
|
//#region src/entity/entity-asset.d.ts
|
|
36
47
|
/** Immutable resource content resolved by the host for a document Entity. */
|
|
@@ -40,6 +51,8 @@ interface EntityAssetContent {
|
|
|
40
51
|
}
|
|
41
52
|
/** Host-only I/O. The sandbox supplies an Entity, never an arbitrary Asset query. */
|
|
42
53
|
type EntityAssetLoader = (docId: string, entity: SandboxEntity) => Promise<EntityAssetContent>;
|
|
54
|
+
/** Program-only initial resource creation; never exposed as a sandbox API. */
|
|
55
|
+
type EntityAssetWriter = (docId: string, entity: SandboxEntity, content: JsonValue) => Promise<EntityAssetContent>;
|
|
43
56
|
//#endregion
|
|
44
57
|
//#region src/entity/entity-sandbox.d.ts
|
|
45
58
|
type DomainIdFactory = (prefix: 'entity' | 'relation') => string;
|
|
@@ -61,6 +74,12 @@ interface ChangePlan {
|
|
|
61
74
|
logs: string[];
|
|
62
75
|
}
|
|
63
76
|
interface EditSandboxSessionOptions {
|
|
77
|
+
/**
|
|
78
|
+
* Document this session edits. Stated by the host: the document content no
|
|
79
|
+
* longer carries its own identity (business metadata left the CRDT), and a
|
|
80
|
+
* plan must name the document it will be committed against.
|
|
81
|
+
*/
|
|
82
|
+
docId?: string;
|
|
64
83
|
idFactory?: PartIdFactory;
|
|
65
84
|
onEntry?: (entry: JournalEntry) => void;
|
|
66
85
|
onLog?: (line: string) => void;
|
|
@@ -87,7 +106,10 @@ interface ConsoleShim {
|
|
|
87
106
|
* injects loaders that break worker boot.
|
|
88
107
|
*/
|
|
89
108
|
interface RunEditScriptOptions {
|
|
109
|
+
/** Document the plan will be committed against; stated by the host. */
|
|
110
|
+
docId: string;
|
|
90
111
|
loadEntityAsset?: (entity: SandboxEntity) => Promise<EntityAssetContent>;
|
|
112
|
+
writeEntityAsset?: (entity: SandboxEntity, content: EntityAssetContent['content']) => Promise<EntityAssetContent>;
|
|
91
113
|
document: VideoDocument;
|
|
92
114
|
baseVersion: string;
|
|
93
115
|
script: string;
|
|
@@ -376,13 +398,6 @@ interface CreateMedeoToolOptions {
|
|
|
376
398
|
userId?: ContextualValue<string>;
|
|
377
399
|
/** Stable agent peer id. Supply a host-scoped value so audit provenance is durable. */
|
|
378
400
|
peerId?: ContextualValue<string>;
|
|
379
|
-
/**
|
|
380
|
-
* Load the authoritative legacy draft used to create a missing Mengine
|
|
381
|
-
* document. The tool owns the get-or-create flow: it first probes Mengine,
|
|
382
|
-
* converts this draft into a VideoDocument only on a 404, bootstraps the
|
|
383
|
-
* snapshot, and tolerates a concurrent creator winning the race.
|
|
384
|
-
*/
|
|
385
|
-
loadInitialDraft?: (docId: string) => Promise<VideoDraft>;
|
|
386
401
|
/**
|
|
387
402
|
* Resolve factual generation lineage by external asset id after a confirmed
|
|
388
403
|
* entity commit. Return every known generation record involving the given
|
|
@@ -395,6 +410,8 @@ interface CreateMedeoToolOptions {
|
|
|
395
410
|
/** Assemble optional Caption artifacts by immutable entity ID; never exposed to scripts. */
|
|
396
411
|
loadCaptionAssets?: CaptionAssetsLoader;
|
|
397
412
|
loadEntityAsset?: EntityAssetLoader;
|
|
413
|
+
/** Program-side persistence for immutable AudioScript resources. */
|
|
414
|
+
writeEntityAsset?: EntityAssetWriter;
|
|
398
415
|
fetchImpl?: typeof fetch;
|
|
399
416
|
/** @deprecated ManualSyncDoc has no SSE or reconnect loop. */
|
|
400
417
|
sseReconnectDelayMs?: number;
|
|
@@ -567,5 +584,5 @@ declare function materializeResources(options: EntityHttpClientOptions & {
|
|
|
567
584
|
loadGenerationFacts?: GenerationFactsLoader;
|
|
568
585
|
}, resources: readonly GeneratedResource[]): Promise<readonly string[]>;
|
|
569
586
|
//#endregion
|
|
570
|
-
export { type AssetGenerationFact, type AuthorableRelationKind, type BusinessEntityFacade, type BusinessRelationFacade, type CaptionAssetAssemblyOutcome, type CaptionAssetFact, type CaptionAssetsLoader, type ChangePlan, type CommitPlan, type CommitPlanOptions, type CommitPlanResult, type CompactProjectionOptions, type ConsoleShim, type CreateEntityInput, type CreateMedeoToolOptions, type DeleteEntityInput, type EditSandboxSessionOptions, type EditScriptResult, type EntityAssetContent, type EntityAssetLoader, type EntityCommand, type EntityCommitResult, type EntityPlanState, type EntitySandboxCheckpoint, type EntityStoreSnapshot, type EntityUpdateInput, type GeneratedResource, type GenerationFactsLoader, type GenerationSyncOutcome, type JsonObject, type JsonPrimitive, type JsonValue, type KnownEntityKind, type KnownRelationKind, type LinkGeneratedRelationInput, type LinkRelationInput, MEDEO_TOOL_DESCRIPTION, MEDEO_TOOL_NAME, MEDEO_TOOL_PARAMETERS, type MedeoCommitResult, type MedeoInitialDraft, type MedeoModelContext, type MedeoModelContextInput, type MedeoTool, type MedeoToolInput, type MedeoToolOp, type MedeoToolResult, type RelationUpdateInput, type ResourceEntityKind, type RunEditScriptOptions, type SandboxEntity, type SandboxRelation, type UnlinkRelationInput, type UpdateEntityInput, collectAffectedPartIds, commitPlan, createMedeoTool, materializeResources, renderCompactProjection, renderPreview, runEditScript };
|
|
587
|
+
export { type AssetGenerationFact, type AuthorableRelationKind, type BusinessEntityFacade, type BusinessRelationFacade, type CaptionAssetAssemblyOutcome, type CaptionAssetFact, type CaptionAssetsLoader, type ChangePlan, type CommitPlan, type CommitPlanOptions, type CommitPlanResult, type CompactProjectionOptions, type ConsoleShim, type CreateEntityInput, type CreateMedeoToolOptions, type DeleteEntityInput, type EditSandboxSessionOptions, type EditScriptResult, type EntityAssetContent, type EntityAssetLoader, type EntityAssetWriter, type EntityCommand, type EntityCommitResult, type EntityPlanState, type EntitySandboxCheckpoint, type EntityStoreSnapshot, type EntityUpdateInput, type GeneratedResource, type GenerationFactsLoader, type GenerationSyncOutcome, type JsonObject, type JsonPrimitive, type JsonValue, type KnownEntityKind, type KnownRelationKind, type LinkGeneratedRelationInput, type LinkRelationInput, MEDEO_TOOL_DESCRIPTION, MEDEO_TOOL_NAME, MEDEO_TOOL_PARAMETERS, type MedeoCommitResult, type MedeoInitialDraft, type MedeoModelContext, type MedeoModelContextInput, type MedeoTool, type MedeoToolInput, type MedeoToolOp, type MedeoToolResult, type RelationUpdateInput, type ResourceEntityKind, type RunEditScriptOptions, type SandboxEntity, type SandboxRelation, type UnlinkRelationInput, type UpdateEntityInput, collectAffectedPartIds, commitPlan, createMedeoTool, materializeResources, renderCompactProjection, renderPreview, runEditScript };
|
|
571
588
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as createEntityId, i as businessState, o as createRelationId, s as isMediaAssetVariantKind, t as EntitySandbox } from "./entity-sandbox-BTR2cRl1.mjs";
|
|
2
|
+
import { ManualSyncDoc, MengineHttpClient, ValidationError, base64ToBytes, bytesToBase64, compileEntityRows, createPlainMemoryAdapter, decodeDocVersionMark, effectiveVideoClipDurationMs, encodeDocVersionMark, replayJournal, solveVideoDocument, speedOf } from "@mengine/medeo-client";
|
|
3
3
|
import { Worker } from "node:worker_threads";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
5
|
//#region src/document/compact-projection.ts
|
|
@@ -79,7 +79,7 @@ function renderCompactProjection(document, options) {
|
|
|
79
79
|
rows.push(`${tag} ${partId} ${lane} [${abs},${abs + dur}) ${attrs}`);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
return [`# draft=${
|
|
82
|
+
return [`# draft=${options?.docId ?? ""} v=${options?.revision ?? 0} duration=${solved.durationMs} parts=${totalParts} shown=${rows.length}`, ...rows].join("\n");
|
|
83
83
|
}
|
|
84
84
|
//#endregion
|
|
85
85
|
//#region src/sandbox/preview.ts
|
|
@@ -132,8 +132,12 @@ function collectFromValue(value, ids) {
|
|
|
132
132
|
* Render a ChangePlan preview: header + rows for journal-affected parts only.
|
|
133
133
|
* Empty journal → empty `onlyPartIds` (header alone), matching the T2 contract.
|
|
134
134
|
*/
|
|
135
|
-
function renderPreview(document, journal) {
|
|
136
|
-
|
|
135
|
+
function renderPreview(document, journal, identity) {
|
|
136
|
+
const onlyPartIds = journal.length === 0 ? /* @__PURE__ */ new Set() : collectAffectedPartIds(journal);
|
|
137
|
+
return renderCompactProjection(document, {
|
|
138
|
+
...identity,
|
|
139
|
+
onlyPartIds
|
|
140
|
+
});
|
|
137
141
|
}
|
|
138
142
|
//#endregion
|
|
139
143
|
//#region src/sandbox/node-host.ts
|
|
@@ -165,6 +169,7 @@ function runEditScript(options) {
|
|
|
165
169
|
let timer;
|
|
166
170
|
const worker = new Worker(workerEntryUrl, {
|
|
167
171
|
workerData: {
|
|
172
|
+
docId: options.docId,
|
|
168
173
|
document: options.document,
|
|
169
174
|
script: options.script,
|
|
170
175
|
inputs: options.inputs,
|
|
@@ -214,11 +219,14 @@ function runEditScript(options) {
|
|
|
214
219
|
armTimeout();
|
|
215
220
|
return;
|
|
216
221
|
}
|
|
217
|
-
if (message.t === "entity-asset") {
|
|
222
|
+
if (message.t === "entity-asset" || message.t === "write-entity-asset") {
|
|
218
223
|
(async () => {
|
|
219
224
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const result = message.t === "write-entity-asset" ? await (options.writeEntityAsset ?? (() => {
|
|
226
|
+
throw new Error("Entity Asset writer is unavailable");
|
|
227
|
+
}))(message.entity, message.content) : await (options.loadEntityAsset ?? (() => {
|
|
228
|
+
throw new Error("Entity Asset loader is unavailable");
|
|
229
|
+
}))(message.entity);
|
|
222
230
|
if (!settled) worker.postMessage({
|
|
223
231
|
t: "entity-asset-result",
|
|
224
232
|
requestId: message.requestId,
|
|
@@ -272,7 +280,7 @@ function runEditScript(options) {
|
|
|
272
280
|
ok: true,
|
|
273
281
|
plan: {
|
|
274
282
|
plan_kind: message.planKind,
|
|
275
|
-
doc_id: options.
|
|
283
|
+
doc_id: options.docId,
|
|
276
284
|
base_version: options.baseVersion,
|
|
277
285
|
ops: ops.slice(),
|
|
278
286
|
...message.loroUpdate ? { loro_update: message.loroUpdate } : {},
|
|
@@ -338,7 +346,7 @@ function runEditScript(options) {
|
|
|
338
346
|
async function assembleCaptionAssets(input) {
|
|
339
347
|
try {
|
|
340
348
|
const state = await input.client.fetchState();
|
|
341
|
-
const candidates = state.entities.filter((entity) => entity.entity_kind === "caption" && !state.relations.some((relation) => relation.relation_kind === "
|
|
349
|
+
const candidates = state.entities.filter((entity) => entity.entity_kind === "caption" && !state.relations.some((relation) => relation.relation_kind === "from-asset" && (relation.endpoint_0_entity_id === entity.entity_id || relation.endpoint_1_entity_id === entity.entity_id)));
|
|
342
350
|
if (!candidates.length) return { status: "current" };
|
|
343
351
|
const ids = new Set(candidates.map((entity) => entity.entity_id));
|
|
344
352
|
const facts = await input.loadAssets(input.docId, [...ids]);
|
|
@@ -353,11 +361,8 @@ async function assembleCaptionAssets(input) {
|
|
|
353
361
|
continue;
|
|
354
362
|
}
|
|
355
363
|
bound.set(fact.captionEntityId, fact.assetId);
|
|
356
|
-
const matches = entities.filter((entity) =>
|
|
357
|
-
|
|
358
|
-
return external !== null && typeof external === "object" && !Array.isArray(external) && external.system === "memota" && external.key === fact.assetId;
|
|
359
|
-
});
|
|
360
|
-
if (matches.length > 1 || matches[0] && matches[0].entity_kind !== "asset") throw new Error(`Conflicting resource identity for Caption Asset ${fact.assetId}`);
|
|
364
|
+
const matches = entities.filter((entity) => entity.entity_kind === "asset" && entity.payload.system === "memota" && entity.payload.key === fact.assetId);
|
|
365
|
+
if (matches.length > 1) throw new Error(`Conflicting resource identity for Caption Asset ${fact.assetId}`);
|
|
361
366
|
let asset = matches[0];
|
|
362
367
|
if (asset && asset.payload.storageKey !== fact.storageKey) throw new Error(`Conflicting storage key for Caption Asset ${fact.assetId}`);
|
|
363
368
|
if (!asset) {
|
|
@@ -365,10 +370,8 @@ async function assembleCaptionAssets(input) {
|
|
|
365
370
|
entity_id: stableId$1("asset", fact.assetId),
|
|
366
371
|
entity_kind: "asset",
|
|
367
372
|
payload: {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
key: fact.assetId
|
|
371
|
-
},
|
|
373
|
+
system: "memota",
|
|
374
|
+
key: fact.assetId,
|
|
372
375
|
storageKey: fact.storageKey
|
|
373
376
|
}
|
|
374
377
|
};
|
|
@@ -377,7 +380,7 @@ async function assembleCaptionAssets(input) {
|
|
|
377
380
|
}
|
|
378
381
|
relations.push({
|
|
379
382
|
relation_id: stableId$1("relation", fact.captionEntityId, asset.entity_id),
|
|
380
|
-
relation_kind: "
|
|
383
|
+
relation_kind: "from-asset",
|
|
381
384
|
endpoint_0_entity_id: fact.captionEntityId,
|
|
382
385
|
endpoint_1_entity_id: asset.entity_id,
|
|
383
386
|
metadata: {},
|
|
@@ -429,11 +432,12 @@ const KNOWN_RELATION_KINDS = [
|
|
|
429
432
|
"marker-content",
|
|
430
433
|
"axvideo-marker",
|
|
431
434
|
"marker-timeline",
|
|
432
|
-
"
|
|
435
|
+
"from-asset",
|
|
433
436
|
"generated",
|
|
434
437
|
"caption-alignment",
|
|
435
438
|
"clip-anchor",
|
|
436
439
|
"phonetic-script-render",
|
|
440
|
+
"voice-timbre",
|
|
437
441
|
"audio-script-source",
|
|
438
442
|
"audio-script-marker"
|
|
439
443
|
];
|
|
@@ -738,25 +742,6 @@ function isRecord$1(value) {
|
|
|
738
742
|
/** @generated by gen:sandbox-dts. DO NOT EDIT. */
|
|
739
743
|
const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
740
744
|
"/** @generated by gen:sandbox-dts. Entity-native editor contract; DO NOT EDIT. */",
|
|
741
|
-
"export interface BoundedDerivedSequencePayload extends JsonObject {",
|
|
742
|
-
" extent: {",
|
|
743
|
-
" kind: 'bounded';",
|
|
744
|
-
" start: number;",
|
|
745
|
-
" end: number;",
|
|
746
|
-
" };",
|
|
747
|
-
" sampling: 'derived';",
|
|
748
|
-
" coordinateSpace: JsonValue;",
|
|
749
|
-
"}",
|
|
750
|
-
"export interface BoundedNativeSequencePayload extends JsonObject {",
|
|
751
|
-
" /** Factual coordinates from recalled media metadata; never invent an end/duration. */",
|
|
752
|
-
" extent: {",
|
|
753
|
-
" kind: 'bounded';",
|
|
754
|
-
" start: number;",
|
|
755
|
-
" end: number;",
|
|
756
|
-
" };",
|
|
757
|
-
" sampling: 'native';",
|
|
758
|
-
" coordinateSpace: JsonValue;",
|
|
759
|
-
"}",
|
|
760
745
|
"/** Business editing surface. Reads are assembled snapshots; writes preserve native operation intent. */",
|
|
761
746
|
"export interface BusinessEntityFacade {",
|
|
762
747
|
" list(): SandboxEntity[];",
|
|
@@ -805,17 +790,20 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
805
790
|
" content: JsonValue;",
|
|
806
791
|
"}",
|
|
807
792
|
"export interface EntityPayloadByKind {",
|
|
808
|
-
"
|
|
793
|
+
" /** Its Timeline decides how long it runs, so it stores no length. */",
|
|
794
|
+
" axvideo: JsonObject;",
|
|
809
795
|
" timeline: JsonObject;",
|
|
810
796
|
" track: JsonObject & {",
|
|
811
797
|
" hidden?: boolean;",
|
|
812
798
|
" role?: string;",
|
|
813
799
|
" };",
|
|
814
800
|
" clip: JsonObject;",
|
|
815
|
-
" video:
|
|
816
|
-
" audio
|
|
817
|
-
"
|
|
818
|
-
"
|
|
801
|
+
" video: OwnDurationPayload;",
|
|
802
|
+
" /** Every playable sound, including video original audio and synthesized voiceovers. */",
|
|
803
|
+
" audio: OwnDurationPayload;",
|
|
804
|
+
" /** A timbre identity, never playable content; the rendered take is an `audio` entity. */",
|
|
805
|
+
" voice: VoiceIdentityPayload;",
|
|
806
|
+
" image: NoDurationPayload;",
|
|
819
807
|
" 'sequence-marker': JsonObject & {",
|
|
820
808
|
" sourceRange: {",
|
|
821
809
|
" start: number;",
|
|
@@ -852,7 +840,7 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
852
840
|
" phonemeScript?: string;",
|
|
853
841
|
" prosody?: JsonObject;",
|
|
854
842
|
" };",
|
|
855
|
-
" caption:
|
|
843
|
+
" caption: OwnDurationPayload & {",
|
|
856
844
|
" baseEntityIds: string[];",
|
|
857
845
|
" selection: CaptionTextSelection;",
|
|
858
846
|
" style?: JsonObject;",
|
|
@@ -942,6 +930,7 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
942
930
|
" | 'caption-alignment'",
|
|
943
931
|
" | 'clip-anchor'",
|
|
944
932
|
" | 'phonetic-script-render'",
|
|
933
|
+
" | 'voice-timbre'",
|
|
945
934
|
" | 'audio-script-source'",
|
|
946
935
|
" | 'audio-script-marker';",
|
|
947
936
|
"interface LinkRelationBase {",
|
|
@@ -954,12 +943,18 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
954
943
|
" relation_kind: KnownRelationKind;",
|
|
955
944
|
" metadata?: JsonObject;",
|
|
956
945
|
"}",
|
|
957
|
-
"
|
|
958
|
-
"
|
|
959
|
-
"
|
|
960
|
-
"
|
|
961
|
-
"
|
|
962
|
-
"
|
|
946
|
+
"/** A still frame runs for as long as its use asks, so it states no length. */",
|
|
947
|
+
"export interface NoDurationPayload extends JsonObject {",
|
|
948
|
+
" durationMs: null;",
|
|
949
|
+
"}",
|
|
950
|
+
"/**",
|
|
951
|
+
" * Content states how long it runs and nothing else: where it is taken from and",
|
|
952
|
+
" * where it lands both belong to its Sequence Marker.",
|
|
953
|
+
" */",
|
|
954
|
+
"export interface OwnDurationPayload extends JsonObject {",
|
|
955
|
+
" /** Factual whole milliseconds from recalled media metadata; never invented. */",
|
|
956
|
+
" durationMs: number;",
|
|
957
|
+
"}",
|
|
963
958
|
"export interface RelationUpdateInput {",
|
|
964
959
|
" relation_id: string;",
|
|
965
960
|
" changes: FieldChange[];",
|
|
@@ -982,22 +977,22 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
982
977
|
" text: string;",
|
|
983
978
|
" language?: string;",
|
|
984
979
|
"};",
|
|
985
|
-
"/**
|
|
980
|
+
"/**",
|
|
981
|
+
" * Stored own fields; a variant may obtain required content fields from its",
|
|
982
|
+
" * declared bases.",
|
|
983
|
+
" *",
|
|
984
|
+
" * Caption and Audio may also be declared resource-only: the ASR transcript and",
|
|
985
|
+
" * the synthesized voiceover are attached by the host, which then fills in the",
|
|
986
|
+
" * remaining factual fields.",
|
|
987
|
+
" */",
|
|
986
988
|
"export type StoredEntityPayload<K extends KnownEntityKind> =",
|
|
987
989
|
" | EntityPayloadByKind[K]",
|
|
988
|
-
"
|
|
990
|
+
" // Resource-only: the host fills the factual fields after reading the Asset.",
|
|
991
|
+
" | (K extends 'caption' | 'audio' ? Record<string, never> : never)",
|
|
989
992
|
" | (JsonObject &",
|
|
990
993
|
" Partial<EntityPayloadByKind[K]> & {",
|
|
991
994
|
" baseEntityIds: string[];",
|
|
992
995
|
" });",
|
|
993
|
-
"export interface UnboundedConstantSequencePayload extends JsonObject {",
|
|
994
|
-
" extent: {",
|
|
995
|
-
" kind: 'unbounded';",
|
|
996
|
-
" start: number;",
|
|
997
|
-
" };",
|
|
998
|
-
" sampling: 'constant';",
|
|
999
|
-
" coordinateSpace: JsonValue;",
|
|
1000
|
-
"}",
|
|
1001
996
|
"export interface UnlinkRelationInput {",
|
|
1002
997
|
" relation_id: string;",
|
|
1003
998
|
"}",
|
|
@@ -1006,9 +1001,17 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
1006
1001
|
" entity_id: string;",
|
|
1007
1002
|
" payload: JsonObject;",
|
|
1008
1003
|
"}",
|
|
1004
|
+
"/** The voice-library timbre a synthesized Audio was rendered with. */",
|
|
1005
|
+
"export type VoiceIdentityPayload = JsonObject & {",
|
|
1006
|
+
" voice: {",
|
|
1007
|
+
" system: 'voice-library';",
|
|
1008
|
+
" key: string;",
|
|
1009
|
+
" name?: string;",
|
|
1010
|
+
" };",
|
|
1011
|
+
"};",
|
|
1009
1012
|
"export declare const entities: BusinessEntityFacade;",
|
|
1010
1013
|
"export declare const relations: BusinessRelationFacade;",
|
|
1011
|
-
"/** Resolve this Entity's attached resource through the host; await before using an uninitialized Caption. */",
|
|
1014
|
+
"/** Resolve this Entity's attached resource through the host; await before using an uninitialized Caption or Voice. */",
|
|
1012
1015
|
"export declare function rgetAssetFromEntity(entityId: string): Promise<EntityAssetContent>;",
|
|
1013
1016
|
"export declare function checkpoint(): EntitySandboxCheckpoint;",
|
|
1014
1017
|
"export declare function rollbackTo(cp: EntitySandboxCheckpoint): void;",
|
|
@@ -1038,19 +1041,21 @@ The host supplies the current document. Do not invent or request a document id.
|
|
|
1038
1041
|
|
|
1039
1042
|
Entity reads return assembled snapshots. Mutating returned objects has no persistence effect. Create declares own fields. Update requires a nonempty changes array and atomically applies its ordered operations to assembled fields, routing each to its declaring owner. DeclareFields explicitly establishes own fields or an override; it is not the normal way to edit inherited content. Deletion requires explicitly handling incident relations, variant references and protected project attachments; it never deletes external resources or cascades business entities.
|
|
1040
1043
|
|
|
1041
|
-
FieldChange paths start at payload for an entity, and at metadata or trace for a relation. Use strings for fields and {elementId:'...'} for list members, never numeric indices. Segment identity is segmentId; reference identity is the referenced id. set writes scalars or schema-declared atomic values, including complete sourceRange, targetRange,
|
|
1044
|
+
FieldChange paths start at payload for an entity, and at metadata or trace for a relation. Use strings for fields and {elementId:'...'} for list members, never numeric indices. Segment identity is segmentId; reference identity is the referenced id. set writes scalars or schema-declared atomic values, including complete sourceRange, targetRange, duration, timeRemapping, selection, textRange and external. It cannot replace collaborative text, lists or maps. Edit text with text.splice (Unicode code-point index and deleteCount), and segments/segmentRanges with list.insert, list.move or list.remove. beforeElementId:null means append; moving before itself, duplicate identity or an absent anchor is an error. Unset removes optional fields only. Map fields are edited through leaf paths; initialize a new own map via declareFields. baseEntityIds is unordered composition membership: insert/remove are supported, moving bases cannot resolve field conflicts. Independent fields and list-item text edits retain native Loro identities.
|
|
1042
1045
|
|
|
1043
1046
|
Entities own data. Relations associate two existing entities; endpoint_0 and endpoint_1 retain their declared positions. Each relation kind defines direction and meaning; neither relation listing order nor endpoint storage order implies a global business order. Relations.update changes metadata/trace fields only; kind/endpoints require explicit unlink plus link with a new identity. Generated relations use generated(output,input). Ordinary associations do not implement composition.
|
|
1044
1047
|
|
|
1045
1048
|
Variants directly hold baseEntityIds. Assembly validates every base before applying explicit own overrides: two bases supplying the same field are an error even if values match or the variant declares that field. Missing/cyclic bases fail. Consumers operate assembled fields without copying inherited content or resolving owners themselves. An owned-field edit creates a new immutable entity version. Base changes automatically advance variant base references and project attachments without versioning an unchanged variant. A variant-owned change versions the variant and retains unchanged bases. Do not manually clone entities for versioning. Use the current plan's ids throughout that execution; refresh after commit.
|
|
1046
1049
|
|
|
1047
|
-
Generation tools return resource references. An asset id
|
|
1050
|
+
Generation tools return resource references. An asset id names an Asset entity ({system:'memota',key:assetId}); every entity made from it links to that Asset with from-asset(entity,asset) and copies no locator of its own. An asset id is never an entity id, baseEntityId or relation endpoint. Physical resources are host infrastructure. The sandbox has no direct resource lookup/CRUD by asset id. rgetAssetFromEntity takes only an existing business entity id and returns {assetId,content} for its attached resource. For output_caption, create a Caption, link it with from-asset to the Asset for that assetId, then await rgetAssetFromEntity(captionId). The host initializes the complete AudioScript and one Caption per resource segment in the same causal plan when selection is omitted. The requested Caption represents the first segment; additional Caption entities share that same script and external reference. After awaiting, discover ALL resource Captions with entities.list filtered by entity_kind and their from-asset Asset, inspect each singular selection/durationMs, and place each in its own Clip/SequenceMarker. Discover them by following from-asset from the Asset. An explicit selection initializes only that selected Caption. Never place the first Caption over the whole transcript; Without explicit bases, the host creates an independent AudioScript for that resource. With bases, it resolves their unique AudioScript text owner and preserves other nonconflicting bases. It never routes text through the panel selection. Read the editable fields with entities.get afterwards. Repeated reads preserve edited text and return the original immutable resource content. Unawaited initialization is completed before validation; failure prevents publication. Resource-free Caption composition from AudioScript is also valid.
|
|
1051
|
+
|
|
1052
|
+
Project initialization creates one current Timeline, the four standard Tracks and one attached AudioScript with segments:[]. A project may contain multiple independent complete AudioScripts (for example a generated script and an uploaded video transcript). The project AudioScript attachment selects only the panel view; it is not a singleton or membership constraint. Locate scripts with entities.list and choose the appropriate source. AudioScript owns independent multi-segment text; each can have multiple Caption variants. Caption and PhoneticScript compose it through baseEntityIds. Caption locates its text as AudioScript plus index: it owns exactly one selection object ({segmentId,textRange?}), never an array or the legacy selections field. segmentId is that segment's stable identity rather than its ordinal, so a concurrent insert or re-segmentation cannot slide a Caption onto other text. It selects one script segment or a contiguous substring, and owns only that selection's intrinsic timing and style; selection textRange is a half-open code-point range and can split display text without rewriting the script. PhoneticScript owns pronunciation/prosody; the voiceover is an Audio rendered from it and related by phonetic-script-render(audio,phoneticScript). Voice is only a timbre identity ({voice:{system:'voice-library',key}}) and is never playable; link the rendered Audio to it with voice-timbre(audio,voice). Speech and caption occupy separate Clips. Caption carries Sequence: it owns its display timing and can be Clip content directly. Subtitles normally accompany sound; use source media or render a voiceover Audio from PhoneticScript when the request requires narration. Do not fabricate a sound resource.
|
|
1048
1053
|
|
|
1049
|
-
|
|
1054
|
+
For Speech resources, create an Audio and link it with from-asset to the Asset {system:'memota-speech',key:speechId}, then await rgetAssetFromEntity(audioId) before linking its placement. The host supplies the factual durationMs and assembles physical storage facts internally; the voice-library identity stays on its own Voice entity. Read the visible fields through entities.get; never invent them or use a Speech ID as a Memota media ID. A TTS voiceover still requires its real PhoneticScript relation; resource loading does not invent script content.
|
|
1050
1055
|
|
|
1051
|
-
AudioScript has no intrinsic time and cannot be Clip content. ASR input Audio/Video
|
|
1056
|
+
Direct AudioScript creation saves its segments as a resource before publication. Its assembled segments remain readable and editable; committed text changes preserve native Loro operations and save a new immutable resource, automatically advancing the external reference. AudioScripts initialized through Caption resources keep inline segments without an additional script resource. Do not manage these resources yourself. AudioScript has no intrinsic time and cannot be Clip content. ASR input Audio/Video associates via audio-script-source(script,source). Its external annotation SequenceMarker associates via audio-script-marker(script,marker) and receives assigned segmentRanges, not references to another marker. Caption intrinsic timing and its Clip's display SequenceMarker are distinct. Move/stretch display by editing only the placement marker.
|
|
1052
1057
|
|
|
1053
|
-
To place content, create a Clip and a SequenceMarker, then link track-clip(track,clip), clip-marker(clip,marker) and marker-content(marker,content). Existing track membership uses timeline-track(timeline,track). The editor has one track of each role: video_clip for Image/Video, speech for
|
|
1058
|
+
To place content, create a Clip and a SequenceMarker, then link track-clip(track,clip), clip-marker(clip,marker) and marker-content(marker,content). Existing track membership uses timeline-track(timeline,track). The editor has one track of each role: video_clip for Image/Video, speech for the voiceover Audio, caption for Caption, bgm for Audio. Content states only its own durationMs in whole milliseconds, never a position: where it is taken from is Marker.sourceRange and where it lands is Marker.targetRange. A still Image states durationMs:null and each use decides how long it runs; an AXVideo stores none because its Timeline decides it. Inspect factual media durations; never invent one. Each placement is exactly one of Clip.order (sequential), Marker.targetRange (absolute) or clip-anchor(childClip,hostClip) plus Marker.anchorOffset. Marker.sourceRange selects source, duration describes playback/display. Whole milliseconds are required by this editor projection. Clip.volume is decibels (-60..20, 0 original). Linear timeRemapping is {kind:'linear',rate:2,mode:'constant'}, with duration matching the rounded source span/rate. BGM can use durationPolicy:'timeline'. Nonlinear speed and multiple overlay tracks are unsupported. Caption visibility is the caption Track's hidden field. Caption style is its style map. Handle structural links explicitly when moving/deleting; there is no implicit cascade.
|
|
1054
1059
|
|
|
1055
1060
|
checkpoint returns an opaque token valid only in this execution. rollbackTo restores that point and invalidates later checkpoints. Returned read snapshots and inputs are not writable document authorities. Use only the declarations below.
|
|
1056
1061
|
`.trim();
|
|
@@ -1213,7 +1218,7 @@ async function commitPlan(doc, plan, options) {
|
|
|
1213
1218
|
* integrity throws are not wrapped.
|
|
1214
1219
|
*/
|
|
1215
1220
|
async function commitPlanPreflight(doc, plan) {
|
|
1216
|
-
const scratch = createPlainMemoryAdapter(doc.snapshot());
|
|
1221
|
+
const scratch = createPlainMemoryAdapter(doc.snapshot(), { readOnly: doc.isEntityDocument() });
|
|
1217
1222
|
for (let index = 0; index < plan.ops.length; index++) {
|
|
1218
1223
|
const entry = plan.ops[index];
|
|
1219
1224
|
if (entry == null) continue;
|
|
@@ -1405,13 +1410,13 @@ function createMedeoTool(options) {
|
|
|
1405
1410
|
const existing = documents.get(docId);
|
|
1406
1411
|
if (existing != null) return await existing;
|
|
1407
1412
|
const created = (async () => {
|
|
1408
|
-
return await
|
|
1413
|
+
return await openDocument(new MengineHttpClient({
|
|
1409
1414
|
docId,
|
|
1410
1415
|
httpOrigin: requiredContext(options.httpOrigin, docId, "httpOrigin"),
|
|
1411
1416
|
...options.authToken !== void 0 ? { authToken: () => optionalContext(options.authToken, docId) } : {},
|
|
1412
1417
|
...options.userId !== void 0 ? { userId: () => optionalContext(options.userId, docId) } : {},
|
|
1413
1418
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
1414
|
-
}),
|
|
1419
|
+
}), optionalContext(options.peerId, docId));
|
|
1415
1420
|
})();
|
|
1416
1421
|
documents.set(docId, created);
|
|
1417
1422
|
try {
|
|
@@ -1451,36 +1456,14 @@ function createMedeoTool(options) {
|
|
|
1451
1456
|
if (documentTails.get(docId) === tail) documentTails.delete(docId);
|
|
1452
1457
|
}
|
|
1453
1458
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
if (options.loadInitialDraft === void 0) throw error;
|
|
1463
|
-
}
|
|
1464
|
-
const document = toVideoDocument(await options.loadInitialDraft(docId));
|
|
1465
|
-
if (Object.keys(document.part_library ?? {}).length > 0) throw new Error("Legacy content cannot bootstrap a Loro entity project");
|
|
1466
|
-
const seed = createMirrorVideoDocument(document, {
|
|
1467
|
-
...peerId !== void 0 ? { peerId } : {},
|
|
1468
|
-
origin: "mengine.medeo_tool.bootstrap"
|
|
1469
|
-
});
|
|
1470
|
-
const foundation = ensureEditorFoundation({
|
|
1471
|
-
entities: [],
|
|
1472
|
-
relations: []
|
|
1473
|
-
});
|
|
1474
|
-
const entities = LoroEntityDocument.create(foundation.rows, {
|
|
1475
|
-
timelineEntityId: foundation.timelineEntityId,
|
|
1476
|
-
audioScriptEntityId: foundation.audioScriptEntityId
|
|
1477
|
-
});
|
|
1478
|
-
seed.import(entities.doc.export({ mode: "snapshot" }));
|
|
1479
|
-
try {
|
|
1480
|
-
await client.bootstrapSnapshot(seed.export({ mode: "snapshot" }));
|
|
1481
|
-
} catch (error) {
|
|
1482
|
-
if (!(error instanceof MengineHttpRequestError) || error.status !== 400) throw error;
|
|
1483
|
-
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Open an existing Mengine document. Creation belongs to Director, which
|
|
1461
|
+
* initializes every project's document (`POST .../initialize`) before any
|
|
1462
|
+
* agent edit; Mengine's `bootstrap` is the internal Legacy-migration route
|
|
1463
|
+
* and is deliberately absent from the client SDK. A 404 here is a real
|
|
1464
|
+
* missing document, not something this tool may paper over.
|
|
1465
|
+
*/
|
|
1466
|
+
async function openDocument(client, peerId) {
|
|
1484
1467
|
return await ManualSyncDoc.open({
|
|
1485
1468
|
client,
|
|
1486
1469
|
...peerId !== void 0 ? { peerId } : {}
|
|
@@ -1652,10 +1635,12 @@ function createMedeoTool(options) {
|
|
|
1652
1635
|
const document = doc.snapshot();
|
|
1653
1636
|
const baseVersion = encodeDocVersionMark(doc.versionMark());
|
|
1654
1637
|
const result = await runEditScript({
|
|
1638
|
+
docId: input.doc_id,
|
|
1655
1639
|
document,
|
|
1656
1640
|
baseVersion,
|
|
1657
1641
|
entityState,
|
|
1658
1642
|
loadEntityAsset: options.loadEntityAsset ? (entity) => options.loadEntityAsset(input.doc_id, entity) : void 0,
|
|
1643
|
+
writeEntityAsset: options.writeEntityAsset ? (entity, content) => options.writeEntityAsset(input.doc_id, entity, content) : void 0,
|
|
1659
1644
|
script: input.script,
|
|
1660
1645
|
...input.inputs !== void 0 ? { inputs: input.inputs } : {},
|
|
1661
1646
|
timeoutMs: input.timeout_ms ?? options.sandbox?.timeoutMs ?? 3e4,
|
|
@@ -1791,20 +1776,24 @@ async function materializeResources(options, resources) {
|
|
|
1791
1776
|
const client = new EntityHttpClient(options);
|
|
1792
1777
|
const state = await client.fetchState();
|
|
1793
1778
|
let resourceKey = "";
|
|
1779
|
+
let mintOrdinal = 0;
|
|
1794
1780
|
const sandbox = new EntitySandbox({
|
|
1795
1781
|
state,
|
|
1796
|
-
idFactory: (prefix) => stableId(prefix, options.docId, resourceKey)
|
|
1782
|
+
idFactory: (prefix) => stableId(prefix, options.docId, resourceKey, String(mintOrdinal++))
|
|
1797
1783
|
});
|
|
1798
1784
|
const ids = [];
|
|
1799
1785
|
for (const resource of resources) {
|
|
1800
1786
|
resourceKey = `${resource.kind}:${resource.assetId}`;
|
|
1787
|
+
mintOrdinal = 0;
|
|
1801
1788
|
if (resource.kind !== "caption") {
|
|
1802
1789
|
ids.push(sandbox.entities.ensureMedia(resource).contentEntityId);
|
|
1803
1790
|
continue;
|
|
1804
1791
|
}
|
|
1805
|
-
const
|
|
1792
|
+
const rows = sandbox.entities.list();
|
|
1793
|
+
const assetRow = rows.find((row) => row.entity_kind === "asset" && row.payload.key === resource.assetId);
|
|
1794
|
+
const existingCaptions = assetRow === void 0 ? [] : rows.filter((row) => row.entity_kind === "caption" && sandbox.entities.assetIdOf(row.entity_id) === assetRow.entity_id);
|
|
1806
1795
|
if (existingCaptions.length) {
|
|
1807
|
-
if (
|
|
1796
|
+
if (assetRow?.payload.storageKey !== resource.storageKey) throw new Error("Conflicting Caption resource identity");
|
|
1808
1797
|
ids.push(...existingCaptions.map((caption) => caption.entity_id));
|
|
1809
1798
|
continue;
|
|
1810
1799
|
}
|
|
@@ -1825,6 +1814,11 @@ async function materializeResources(options, resources) {
|
|
|
1825
1814
|
entity_kind: "audio-script",
|
|
1826
1815
|
payload: { segments }
|
|
1827
1816
|
});
|
|
1817
|
+
const captionAssetId = sandbox.entities.ensureAsset({
|
|
1818
|
+
system: "memota",
|
|
1819
|
+
key: resource.assetId,
|
|
1820
|
+
storageKey: resource.storageKey
|
|
1821
|
+
});
|
|
1828
1822
|
const ranges = resource.segments.map((segment, index) => ({
|
|
1829
1823
|
segmentId: segments[index].segmentId,
|
|
1830
1824
|
startMs: segment.startMs,
|
|
@@ -1837,22 +1831,16 @@ async function materializeResources(options, resources) {
|
|
|
1837
1831
|
entity_kind: "caption",
|
|
1838
1832
|
payload: {
|
|
1839
1833
|
baseEntityIds: [scriptId],
|
|
1840
|
-
external: {
|
|
1841
|
-
system: "memota",
|
|
1842
|
-
key: resource.assetId
|
|
1843
|
-
},
|
|
1844
|
-
storageKey: resource.storageKey,
|
|
1845
1834
|
selection: { segmentId: range.segmentId },
|
|
1846
1835
|
segmentRanges: [range],
|
|
1847
|
-
|
|
1848
|
-
kind: "bounded",
|
|
1849
|
-
start: range.startMs,
|
|
1850
|
-
end: range.endMs
|
|
1851
|
-
},
|
|
1852
|
-
sampling: "native",
|
|
1853
|
-
coordinateSpace: "milliseconds"
|
|
1836
|
+
durationMs: range.endMs - range.startMs
|
|
1854
1837
|
}
|
|
1855
1838
|
});
|
|
1839
|
+
sandbox.relations.link({
|
|
1840
|
+
relation_kind: "from-asset",
|
|
1841
|
+
endpoint_0_entity_id: captionId,
|
|
1842
|
+
endpoint_1_entity_id: captionAssetId
|
|
1843
|
+
});
|
|
1856
1844
|
ids.push(captionId);
|
|
1857
1845
|
}
|
|
1858
1846
|
const markerId = sandbox.entities.create({
|