@mengine/medeo-tool 2.0.1-alpha.6 → 2.0.1-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{entity-contract-G4HxwNXl.d.mts → entity-contract-Bpe6NRIQ.d.mts} +23 -6
- package/dist/{entity-sandbox-DSFbfybl.mjs → entity-sandbox-CzaUmSsS.mjs} +104 -28
- package/dist/entity-sandbox-CzaUmSsS.mjs.map +1 -0
- package/dist/index.d.mts +21 -9
- package/dist/index.mjs +52 -47
- package/dist/index.mjs.map +1 -1
- package/dist/sandbox-api.d.mts +22 -5
- package/dist/worker-entry.d.mts +2 -1
- package/dist/worker-entry.mjs +24 -25
- package/dist/worker-entry.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/entity-sandbox-DSFbfybl.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as isMediaAssetVariantKind, i as businessState, o as createEntityId, s as createRelationId, t as EntitySandbox } from "./entity-sandbox-
|
|
2
|
-
import {
|
|
1
|
+
import { c as isMediaAssetVariantKind, i as businessState, o as createEntityId, s as createRelationId, t as EntitySandbox } from "./entity-sandbox-CzaUmSsS.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,
|
|
@@ -275,7 +280,7 @@ function runEditScript(options) {
|
|
|
275
280
|
ok: true,
|
|
276
281
|
plan: {
|
|
277
282
|
plan_kind: message.planKind,
|
|
278
|
-
doc_id: options.
|
|
283
|
+
doc_id: options.docId,
|
|
279
284
|
base_version: options.baseVersion,
|
|
280
285
|
ops: ops.slice(),
|
|
281
286
|
...message.loroUpdate ? { loro_update: message.loroUpdate } : {},
|
|
@@ -437,6 +442,7 @@ const KNOWN_RELATION_KINDS = [
|
|
|
437
442
|
"caption-alignment",
|
|
438
443
|
"clip-anchor",
|
|
439
444
|
"phonetic-script-render",
|
|
445
|
+
"voice-timbre",
|
|
440
446
|
"audio-script-source",
|
|
441
447
|
"audio-script-marker"
|
|
442
448
|
];
|
|
@@ -816,8 +822,10 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
816
822
|
" };",
|
|
817
823
|
" clip: JsonObject;",
|
|
818
824
|
" video: BoundedNativeSequencePayload & MediaAssetPayload;",
|
|
825
|
+
" /** Every playable sound, including video original audio and synthesized voiceovers. */",
|
|
819
826
|
" audio: BoundedNativeSequencePayload & MediaAssetPayload;",
|
|
820
|
-
"
|
|
827
|
+
" /** A timbre identity, never playable content; the rendered take is an `audio` entity. */",
|
|
828
|
+
" voice: VoiceIdentityPayload;",
|
|
821
829
|
" image: UnboundedConstantSequencePayload & MediaAssetPayload;",
|
|
822
830
|
" 'sequence-marker': JsonObject & {",
|
|
823
831
|
" sourceRange: {",
|
|
@@ -945,6 +953,7 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
945
953
|
" | 'caption-alignment'",
|
|
946
954
|
" | 'clip-anchor'",
|
|
947
955
|
" | 'phonetic-script-render'",
|
|
956
|
+
" | 'voice-timbre'",
|
|
948
957
|
" | 'audio-script-source'",
|
|
949
958
|
" | 'audio-script-marker';",
|
|
950
959
|
"interface LinkRelationBase {",
|
|
@@ -985,10 +994,17 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
985
994
|
" text: string;",
|
|
986
995
|
" language?: string;",
|
|
987
996
|
"};",
|
|
988
|
-
"/**
|
|
997
|
+
"/**",
|
|
998
|
+
" * Stored own fields; a variant may obtain required content fields from its",
|
|
999
|
+
" * declared bases.",
|
|
1000
|
+
" *",
|
|
1001
|
+
" * Caption and Audio may also be declared resource-only: the ASR transcript and",
|
|
1002
|
+
" * the synthesized voiceover are attached by the host, which then fills in the",
|
|
1003
|
+
" * remaining factual fields.",
|
|
1004
|
+
" */",
|
|
989
1005
|
"export type StoredEntityPayload<K extends KnownEntityKind> =",
|
|
990
1006
|
" | EntityPayloadByKind[K]",
|
|
991
|
-
" | (K extends 'caption' | '
|
|
1007
|
+
" | (K extends 'caption' | 'audio' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never)",
|
|
992
1008
|
" | (JsonObject &",
|
|
993
1009
|
" Partial<EntityPayloadByKind[K]> & {",
|
|
994
1010
|
" baseEntityIds: string[];",
|
|
@@ -1009,6 +1025,14 @@ const ENTITY_EDIT_SANDBOX_API_DTS = [
|
|
|
1009
1025
|
" entity_id: string;",
|
|
1010
1026
|
" payload: JsonObject;",
|
|
1011
1027
|
"}",
|
|
1028
|
+
"/** The voice-library timbre a synthesized Audio was rendered with. */",
|
|
1029
|
+
"export type VoiceIdentityPayload = JsonObject & {",
|
|
1030
|
+
" voice: {",
|
|
1031
|
+
" system: 'voice-library';",
|
|
1032
|
+
" key: string;",
|
|
1033
|
+
" name?: string;",
|
|
1034
|
+
" };",
|
|
1035
|
+
"};",
|
|
1012
1036
|
"export declare const entities: BusinessEntityFacade;",
|
|
1013
1037
|
"export declare const relations: BusinessRelationFacade;",
|
|
1014
1038
|
"/** Resolve this Entity's attached resource through the host; await before using an uninitialized Caption or Voice. */",
|
|
@@ -1049,13 +1073,13 @@ Variants directly hold baseEntityIds. Assembly validates every base before apply
|
|
|
1049
1073
|
|
|
1050
1074
|
Generation tools return resource references. An asset id may be passed in an entity's external:{system:'memota',key:assetId}; it 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 with payload:{external:{system:'memota',key: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 payload.external.key, inspect each singular selection/extent, and place each in its own Clip/SequenceMarker. 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
1075
|
|
|
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 owns exactly one selection object ({segmentId,textRange?}), never an array or the legacy selections field. It selects one script segment or a contiguous substring, and owns only that selection's intrinsic timing and style; segmentId is a stable script-local identity, not an entity reference or transient array index; selection textRange is a half-open code-point range and can split display text without rewriting the script. PhoneticScript owns pronunciation/prosody;
|
|
1076
|
+
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 owns exactly one selection object ({segmentId,textRange?}), never an array or the legacy selections field. It selects one script segment or a contiguous substring, and owns only that selection's intrinsic timing and style; segmentId is a stable script-local identity, not an entity reference or transient array index; 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.
|
|
1053
1077
|
|
|
1054
|
-
For Speech resources, create
|
|
1078
|
+
For Speech resources, create an Audio with external:{system:'memota-speech',key:speechId}, then await rgetAssetFromEntity(audioId) before linking its placement. The host supplies full extent 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.
|
|
1055
1079
|
|
|
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
|
|
1080
|
+
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.
|
|
1057
1081
|
|
|
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
|
|
1082
|
+
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. Inspect factual media extents; never invent duration. 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). Image is unbounded/constant. 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.
|
|
1059
1083
|
|
|
1060
1084
|
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.
|
|
1061
1085
|
`.trim();
|
|
@@ -1218,7 +1242,7 @@ async function commitPlan(doc, plan, options) {
|
|
|
1218
1242
|
* integrity throws are not wrapped.
|
|
1219
1243
|
*/
|
|
1220
1244
|
async function commitPlanPreflight(doc, plan) {
|
|
1221
|
-
const scratch = createPlainMemoryAdapter(doc.snapshot());
|
|
1245
|
+
const scratch = createPlainMemoryAdapter(doc.snapshot(), { readOnly: doc.isEntityDocument() });
|
|
1222
1246
|
for (let index = 0; index < plan.ops.length; index++) {
|
|
1223
1247
|
const entry = plan.ops[index];
|
|
1224
1248
|
if (entry == null) continue;
|
|
@@ -1410,13 +1434,13 @@ function createMedeoTool(options) {
|
|
|
1410
1434
|
const existing = documents.get(docId);
|
|
1411
1435
|
if (existing != null) return await existing;
|
|
1412
1436
|
const created = (async () => {
|
|
1413
|
-
return await
|
|
1437
|
+
return await openDocument(new MengineHttpClient({
|
|
1414
1438
|
docId,
|
|
1415
1439
|
httpOrigin: requiredContext(options.httpOrigin, docId, "httpOrigin"),
|
|
1416
1440
|
...options.authToken !== void 0 ? { authToken: () => optionalContext(options.authToken, docId) } : {},
|
|
1417
1441
|
...options.userId !== void 0 ? { userId: () => optionalContext(options.userId, docId) } : {},
|
|
1418
1442
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
1419
|
-
}),
|
|
1443
|
+
}), optionalContext(options.peerId, docId));
|
|
1420
1444
|
})();
|
|
1421
1445
|
documents.set(docId, created);
|
|
1422
1446
|
try {
|
|
@@ -1456,36 +1480,14 @@ function createMedeoTool(options) {
|
|
|
1456
1480
|
if (documentTails.get(docId) === tail) documentTails.delete(docId);
|
|
1457
1481
|
}
|
|
1458
1482
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (options.loadInitialDraft === void 0) throw error;
|
|
1468
|
-
}
|
|
1469
|
-
const document = toVideoDocument(await options.loadInitialDraft(docId));
|
|
1470
|
-
if (Object.keys(document.part_library ?? {}).length > 0) throw new Error("Legacy content cannot bootstrap a Loro entity project");
|
|
1471
|
-
const seed = createMirrorVideoDocument(document, {
|
|
1472
|
-
...peerId !== void 0 ? { peerId } : {},
|
|
1473
|
-
origin: "mengine.medeo_tool.bootstrap"
|
|
1474
|
-
});
|
|
1475
|
-
const foundation = ensureEditorFoundation({
|
|
1476
|
-
entities: [],
|
|
1477
|
-
relations: []
|
|
1478
|
-
});
|
|
1479
|
-
const entities = LoroEntityDocument.create(foundation.rows, {
|
|
1480
|
-
timelineEntityId: foundation.timelineEntityId,
|
|
1481
|
-
audioScriptEntityId: foundation.audioScriptEntityId
|
|
1482
|
-
});
|
|
1483
|
-
seed.import(entities.doc.export({ mode: "snapshot" }));
|
|
1484
|
-
try {
|
|
1485
|
-
await client.bootstrapSnapshot(seed.export({ mode: "snapshot" }));
|
|
1486
|
-
} catch (error) {
|
|
1487
|
-
if (!(error instanceof MengineHttpRequestError) || error.status !== 400) throw error;
|
|
1488
|
-
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Open an existing Mengine document. Creation belongs to Director, which
|
|
1485
|
+
* initializes every project's document (`POST .../initialize`) before any
|
|
1486
|
+
* agent edit; Mengine's `bootstrap` is the internal Legacy-migration route
|
|
1487
|
+
* and is deliberately absent from the client SDK. A 404 here is a real
|
|
1488
|
+
* missing document, not something this tool may paper over.
|
|
1489
|
+
*/
|
|
1490
|
+
async function openDocument(client, peerId) {
|
|
1489
1491
|
return await ManualSyncDoc.open({
|
|
1490
1492
|
client,
|
|
1491
1493
|
...peerId !== void 0 ? { peerId } : {}
|
|
@@ -1657,6 +1659,7 @@ function createMedeoTool(options) {
|
|
|
1657
1659
|
const document = doc.snapshot();
|
|
1658
1660
|
const baseVersion = encodeDocVersionMark(doc.versionMark());
|
|
1659
1661
|
const result = await runEditScript({
|
|
1662
|
+
docId: input.doc_id,
|
|
1660
1663
|
document,
|
|
1661
1664
|
baseVersion,
|
|
1662
1665
|
entityState,
|
|
@@ -1797,13 +1800,15 @@ async function materializeResources(options, resources) {
|
|
|
1797
1800
|
const client = new EntityHttpClient(options);
|
|
1798
1801
|
const state = await client.fetchState();
|
|
1799
1802
|
let resourceKey = "";
|
|
1803
|
+
let mintOrdinal = 0;
|
|
1800
1804
|
const sandbox = new EntitySandbox({
|
|
1801
1805
|
state,
|
|
1802
|
-
idFactory: (prefix) => stableId(prefix, options.docId, resourceKey)
|
|
1806
|
+
idFactory: (prefix) => stableId(prefix, options.docId, resourceKey, String(mintOrdinal++))
|
|
1803
1807
|
});
|
|
1804
1808
|
const ids = [];
|
|
1805
1809
|
for (const resource of resources) {
|
|
1806
1810
|
resourceKey = `${resource.kind}:${resource.assetId}`;
|
|
1811
|
+
mintOrdinal = 0;
|
|
1807
1812
|
if (resource.kind !== "caption") {
|
|
1808
1813
|
ids.push(sandbox.entities.ensureMedia(resource).contentEntityId);
|
|
1809
1814
|
continue;
|