@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/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-DSFbfybl.mjs";
2
- import { LoroEntityDocument, ManualSyncDoc, MengineHttpClient, MengineHttpRequestError, ValidationError, base64ToBytes, bytesToBase64, compileEntityRows, createMirrorVideoDocument, createPlainMemoryAdapter, decodeDocVersionMark, effectiveVideoClipDurationMs, encodeDocVersionMark, ensureEditorFoundation, replayJournal, solveVideoDocument, speedOf, toVideoDocument } from "@mengine/medeo-client";
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=${document.meta.draft_id ?? ""} v=${document.meta.version ?? 0} duration=${solved.durationMs} parts=${totalParts} shown=${rows.length}`, ...rows].join("\n");
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
- return renderCompactProjection(document, { onlyPartIds: journal.length === 0 ? /* @__PURE__ */ new Set() : collectAffectedPartIds(journal) });
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.document.meta.draft_id ?? "",
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
- " voice: BoundedNativeSequencePayload & MediaAssetPayload;",
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
- "/** Stored own fields; a variant may obtain required content fields from its declared bases. */",
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' | 'voice' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never)",
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; Voice is generated from it and related by phonetic-script-render(voice,phoneticScript). Speech and caption occupy separate Clips. Subtitles normally accompany sound; use source media or generate Voice from PhoneticScript when the request requires narration. Do not fabricate a sound resource.
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 a Voice with external:{system:'memota-speech',key:speechId}, then await rgetAssetFromEntity(voiceId) before linking its placement. The host supplies full extent and optional voice-library identity, and assembles physical storage facts internally. Read the visible fields through entities.get; never invent them or use a Speech ID as a Memota media ID. TTS Voice still requires its real PhoneticScript relation; resource loading does not invent script content.
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/Voice 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.
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 Voice, 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.
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 getOrCreateDocument(new MengineHttpClient({
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
- }), docId, optionalContext(options.peerId, docId));
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
- async function getOrCreateDocument(client, docId, peerId) {
1460
- try {
1461
- return await ManualSyncDoc.open({
1462
- client,
1463
- ...peerId !== void 0 ? { peerId } : {}
1464
- });
1465
- } catch (error) {
1466
- if (!(error instanceof MengineHttpRequestError) || error.status !== 404) throw error;
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;