@mengine/medeo-client 2.0.1-alpha.22 → 2.0.1-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-BB8U25jt.js → document-7Vi1AeQc.js} +168 -47
- package/dist/{index-C0uUWqkz.d.ts → index-mGObzOh4.d.ts} +361 -78
- package/dist/index.d.ts +51 -5
- package/dist/index.js +181 -24
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +4 -4
|
@@ -1177,7 +1177,8 @@ function toAuthoritativePartLibrary(partLibrary) {
|
|
|
1177
1177
|
if (typeof part !== "object" || part == null) continue;
|
|
1178
1178
|
if (part.video_clip != null) out[partId] = { video_clip: withTrimWindowFromDuration(part.video_clip) };
|
|
1179
1179
|
else if (part.speech != null) {
|
|
1180
|
-
const {
|
|
1180
|
+
const { origin_asset_system: _originAssetSystem, ...readSpeech } = part.speech;
|
|
1181
|
+
const { duration_ms, rest } = splitDurationMs(readSpeech);
|
|
1181
1182
|
out[partId] = { speech: {
|
|
1182
1183
|
...rest,
|
|
1183
1184
|
media_duration_ms: rest.media_duration_ms ?? duration_ms
|
|
@@ -2462,7 +2463,7 @@ const axVideoMarkerRelationSpec = emptySpec("axvideo-marker", "axvideo", "sequen
|
|
|
2462
2463
|
const markerTimelineRelationSpec = emptySpec("marker-timeline", "sequence-marker", "timeline");
|
|
2463
2464
|
const markerContentRelationSpec = Object.freeze({
|
|
2464
2465
|
kind: "marker-content",
|
|
2465
|
-
validateEndpoints: (endpoints) =>
|
|
2466
|
+
validateEndpoints: (endpoints) => endpoints[0].current().entityKind === "sequence-marker" && hasSequence(endpoints[1].current()),
|
|
2466
2467
|
validateMetadata: isEmptyMetadata
|
|
2467
2468
|
});
|
|
2468
2469
|
/**
|
|
@@ -2476,7 +2477,7 @@ const markerContentRelationSpec = Object.freeze({
|
|
|
2476
2477
|
*/
|
|
2477
2478
|
const fromAssetRelationSpec = Object.freeze({
|
|
2478
2479
|
kind: "from-asset",
|
|
2479
|
-
validateEndpoints: (endpoints) =>
|
|
2480
|
+
validateEndpoints: (endpoints) => declaresKind(endpoints[0].current().entityKind, "MediaFile") && endpoints[1].current().entityKind === "asset",
|
|
2480
2481
|
validateMetadata: isAssetBinding
|
|
2481
2482
|
});
|
|
2482
2483
|
/**
|
|
@@ -2496,13 +2497,13 @@ const fromAssetRelationSpec = Object.freeze({
|
|
|
2496
2497
|
*/
|
|
2497
2498
|
const toAssetRelationSpec = Object.freeze({
|
|
2498
2499
|
kind: "to-asset",
|
|
2499
|
-
validateEndpoints: (endpoints) =>
|
|
2500
|
+
validateEndpoints: (endpoints) => declaresKind(endpoints[0].current().entityKind, "MediaFile") && endpoints[1].current().entityKind === "asset",
|
|
2500
2501
|
validateMetadata: isAssetBinding
|
|
2501
2502
|
});
|
|
2502
|
-
/** `generated(output, input)`
|
|
2503
|
+
/** `generated(output, input)` records media generation or an independent AudioScript revision. */
|
|
2503
2504
|
const generatedRelationSpec = Object.freeze({
|
|
2504
2505
|
kind: "generated",
|
|
2505
|
-
validateEndpoints: (endpoints) => endpoints.every((endpoint) => isGeneratedMedia(endpoint.current())),
|
|
2506
|
+
validateEndpoints: (endpoints) => endpoints.every((endpoint) => isGeneratedMedia(endpoint.current())) || endpoints.every((endpoint) => endpoint.current().entityKind === "audio-script"),
|
|
2506
2507
|
validateMetadata: isEmptyMetadata
|
|
2507
2508
|
});
|
|
2508
2509
|
const captionAlignmentRelationSpec = Object.freeze({
|
|
@@ -2517,8 +2518,8 @@ const clipAnchorRelationSpec = Object.freeze({
|
|
|
2517
2518
|
validateMetadata: isEmptyMetadata
|
|
2518
2519
|
});
|
|
2519
2520
|
/**
|
|
2520
|
-
* The Voiceover and the PhoneticScript it was spoken from
|
|
2521
|
-
*
|
|
2521
|
+
* The Voiceover and the PhoneticScript it was spoken from. Endpoint 0 is the
|
|
2522
|
+
* output and endpoint 1 is the input script.
|
|
2522
2523
|
*
|
|
2523
2524
|
* Every Voiceover must carry one: which words were said, and how, is the only
|
|
2524
2525
|
* thing that makes speech reproducible, and a Voiceover that cannot answer it
|
|
@@ -2539,7 +2540,7 @@ const voiceTimbreRelationSpec = Object.freeze({
|
|
|
2539
2540
|
validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["voiceover"]), new Set(["voice"])),
|
|
2540
2541
|
validateMetadata: isEmptyMetadata
|
|
2541
2542
|
});
|
|
2542
|
-
/** AudioScript was transcribed from Audio
|
|
2543
|
+
/** AudioScript was transcribed from Audio or Video: `[script, source]`. */
|
|
2543
2544
|
const audioScriptSourceRelationSpec = Object.freeze({
|
|
2544
2545
|
kind: "audio-script-source",
|
|
2545
2546
|
validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio-script"]), new Set([
|
|
@@ -2591,19 +2592,7 @@ function metadataSpec(kind, a, b, validateMetadata) {
|
|
|
2591
2592
|
function hasKinds(endpoints, aKinds, bKinds) {
|
|
2592
2593
|
const first = endpoints[0].current().entityKind;
|
|
2593
2594
|
const second = endpoints[1].current().entityKind;
|
|
2594
|
-
return aKinds.has(first) && bKinds.has(second)
|
|
2595
|
-
}
|
|
2596
|
-
function hasMarkerAndSequence(endpoints) {
|
|
2597
|
-
const first = endpoints[0].current();
|
|
2598
|
-
const second = endpoints[1].current();
|
|
2599
|
-
return first.entityKind === "sequence-marker" && hasSequence(second) || second.entityKind === "sequence-marker" && hasSequence(first);
|
|
2600
|
-
}
|
|
2601
|
-
/** Exactly one end is the Asset, so the other end is unambiguously the bound entity. */
|
|
2602
|
-
function hasExactlyOneAsset(endpoints) {
|
|
2603
|
-
const kinds = endpoints.map((endpoint) => endpoint.current().entityKind);
|
|
2604
|
-
const assets = kinds.map((kind) => kind === "asset");
|
|
2605
|
-
if (assets[0] === assets[1]) return false;
|
|
2606
|
-
return declaresKind(assets[0] ? kinds[1] : kinds[0], "MediaFile");
|
|
2595
|
+
return aKinds.has(first) && bKinds.has(second);
|
|
2607
2596
|
}
|
|
2608
2597
|
function isGeneratedMedia(entity) {
|
|
2609
2598
|
return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio" || entity.entityKind === "voiceover";
|
|
@@ -2939,6 +2928,42 @@ function errorMessage(error) {
|
|
|
2939
2928
|
return error instanceof Error ? error.message : String(error);
|
|
2940
2929
|
}
|
|
2941
2930
|
//#endregion
|
|
2931
|
+
//#region ../medeo-dsl/src/relation-transition.ts
|
|
2932
|
+
var InvalidEntityRelationTransitionError = class extends Error {
|
|
2933
|
+
issues;
|
|
2934
|
+
constructor(issues) {
|
|
2935
|
+
super(`Invalid Medeo relation transition:\n- ${issues.join("\n- ")}`);
|
|
2936
|
+
this.issues = issues;
|
|
2937
|
+
this.name = "InvalidEntityRelationTransitionError";
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
/** Enforce relation fact identity while admitting a complete candidate view. */
|
|
2941
|
+
function validateEntityRelationTransition(before, candidate) {
|
|
2942
|
+
const issues = [];
|
|
2943
|
+
const previous = new Map(before.relations.map((row) => [row.relationId, row]));
|
|
2944
|
+
for (const relation of candidate.relations) {
|
|
2945
|
+
const original = previous.get(relation.relationId);
|
|
2946
|
+
if (original == null) {
|
|
2947
|
+
if (relation.relationKind === "caption-alignment") issues.push(`new caption-alignment relation "${relation.relationId}" is forbidden`);
|
|
2948
|
+
continue;
|
|
2949
|
+
}
|
|
2950
|
+
if (original.relationKind !== relation.relationKind || original.endpoint0EntityId !== relation.endpoint0EntityId || original.endpoint1EntityId !== relation.endpoint1EntityId) {
|
|
2951
|
+
issues.push(`relation "${relation.relationId}" changed its kind or ordered endpoints`);
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
if (!sameJson(original.metadata, relation.metadata)) issues.push(`relation "${relation.relationId}" changed semantic metadata without a new identity`);
|
|
2955
|
+
if (relation.relationKind === "caption-alignment" && !sameJson(original.trace, relation.trace)) issues.push(`legacy caption-alignment relation "${relation.relationId}" may only be carried unchanged or deleted`);
|
|
2956
|
+
}
|
|
2957
|
+
if (issues.length > 0) throw new InvalidEntityRelationTransitionError(issues);
|
|
2958
|
+
}
|
|
2959
|
+
function sameJson(left, right) {
|
|
2960
|
+
if (left === right) return true;
|
|
2961
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((value, index) => sameJson(value, right[index]));
|
|
2962
|
+
if (!isJsonObject(left) || !isJsonObject(right)) return false;
|
|
2963
|
+
const leftKeys = Object.keys(left);
|
|
2964
|
+
return leftKeys.length === Object.keys(right).length && leftKeys.every((key) => Object.hasOwn(right, key) && sameJson(left[key], right[key]));
|
|
2965
|
+
}
|
|
2966
|
+
//#endregion
|
|
2942
2967
|
//#region src/document/entity-projection-values.ts
|
|
2943
2968
|
/** A graph cannot be represented faithfully by the compatibility reader. */
|
|
2944
2969
|
var EntityTimelineProjectionError = class extends Error {
|
|
@@ -3035,8 +3060,7 @@ function resolveGraphLayout(graph) {
|
|
|
3035
3060
|
if (clip.entityKind !== "clip" || graph.adjacent(clip.entityId, "track-clip").length !== 1) fail(`Clip ${clip.entityId} must belong to exactly one Track`);
|
|
3036
3061
|
const marker = graph.one(clip.entityId, "clip-marker", "sequence-marker");
|
|
3037
3062
|
const content = graph.one(marker.entityId, "marker-content");
|
|
3038
|
-
|
|
3039
|
-
if (!expected.includes(content.entityKind)) fail(`${typedRole} Track accepts ${expected.join(" or ")}, got ${content.entityKind}`);
|
|
3063
|
+
assertTrackContent(graph, typedRole, content);
|
|
3040
3064
|
const source = range(marker.payload.sourceRange, "sourceRange");
|
|
3041
3065
|
const durationFact = marker.payload.duration;
|
|
3042
3066
|
if (!object(durationFact)) fail(`Marker ${marker.entityId} requires duration`);
|
|
@@ -3155,6 +3179,33 @@ function resolveGraphLayout(graph) {
|
|
|
3155
3179
|
}))
|
|
3156
3180
|
};
|
|
3157
3181
|
}
|
|
3182
|
+
function assertTrackContent(graph, role, content) {
|
|
3183
|
+
if (role === "speech") {
|
|
3184
|
+
assertSpeechContent(graph, content);
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
const expected = role === "video_clip" ? ["image", "video"] : role === "caption" ? ["caption"] : ["audio"];
|
|
3188
|
+
if (!expected.includes(content.entityKind)) fail(`${role} Track accepts ${expected.join(" or ")}, got ${content.entityKind}`);
|
|
3189
|
+
}
|
|
3190
|
+
function assertSpeechContent(graph, content) {
|
|
3191
|
+
const assetLinks = graph.rows.relations.filter((relation) => relation.relationKind === "from-asset" && (relation.endpoint0EntityId === content.entityId || relation.endpoint1EntityId === content.entityId));
|
|
3192
|
+
if (assetLinks.length !== 1 || assetLinks[0].endpoint0EntityId !== content.entityId) fail("Speech content requires one ordered from-asset binding");
|
|
3193
|
+
const asset = graph.entity(assetLinks[0].endpoint1EntityId, "asset");
|
|
3194
|
+
if (content.entityKind === "voiceover") {
|
|
3195
|
+
const renders = graph.rows.relations.filter((relation) => relation.relationKind === "phonetic-script-render" && (relation.endpoint0EntityId === content.entityId || relation.endpoint1EntityId === content.entityId));
|
|
3196
|
+
if (renders.length !== 1 || renders[0].endpoint0EntityId !== content.entityId || graph.entity(renders[0].endpoint1EntityId).entityKind !== "phonetic-script") fail("Synthesized Speech Voiceover requires one ordered PhoneticScript render");
|
|
3197
|
+
if (asset.payload.system !== "memota-speech") fail("Synthesized Speech Voiceover requires an explicit memota-speech key");
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
if (content.entityKind === "audio") {
|
|
3201
|
+
const sources = graph.rows.relations.filter((relation) => relation.relationKind === "audio-script-source" && (relation.endpoint0EntityId === content.entityId || relation.endpoint1EntityId === content.entityId));
|
|
3202
|
+
if (sources.length > 1) fail("Recorded speech has an ambiguous AudioScript source receipt");
|
|
3203
|
+
if (sources.length === 1 && (sources[0].endpoint1EntityId !== content.entityId || graph.entity(sources[0].endpoint0EntityId).entityKind !== "audio-script")) fail("Recorded Speech AudioScript source must use ordered endpoints");
|
|
3204
|
+
if (asset.payload.system !== "memota") fail("Recorded Speech Audio requires an explicit memota key");
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
fail(`speech Track accepts voiceover or audio, got ${content.entityKind}`);
|
|
3208
|
+
}
|
|
3158
3209
|
//#endregion
|
|
3159
3210
|
//#region src/document/entity-projection.ts
|
|
3160
3211
|
/**
|
|
@@ -3196,20 +3247,21 @@ function projectEntityTimeline(rows) {
|
|
|
3196
3247
|
} else if (track.role === "speech") {
|
|
3197
3248
|
const asset = mediaAssetFacts(graph, content);
|
|
3198
3249
|
requireUntrimmedAudio(content, marker, placement);
|
|
3199
|
-
const external = externalLocator(asset, "memota-speech");
|
|
3200
|
-
const renderEdges = graph.adjacent(content.entityId, "phonetic-script-render");
|
|
3201
3250
|
const timbreEdges = graph.adjacent(content.entityId, "voice-timbre");
|
|
3202
|
-
const
|
|
3251
|
+
const isSynthesized = content.entityKind === "voiceover";
|
|
3252
|
+
const external = externalLocator(asset, isSynthesized ? "memota-speech" : "memota");
|
|
3253
|
+
const assembled = isSynthesized ? assemblePhoneticScriptContent(rows, graph.one(content.entityId, "phonetic-script-render", "phonetic-script").entityId) : void 0;
|
|
3203
3254
|
if (timbreEdges.length > 1) fail("A voiceover resolves at most one Voice identity");
|
|
3204
3255
|
const voice = timbreEdges.length === 0 ? void 0 : graph.one(content.entityId, "voice-timbre", "voice").payload.voice;
|
|
3205
3256
|
if (voice !== void 0 && (!object(voice) || voice.system !== "voice-library" || typeof voice.key !== "string")) fail("Voice must use an explicit voice-library locator");
|
|
3206
3257
|
const captionIds = layout.tracks.flatMap((candidate) => candidate.role === "caption" ? candidate.clips.filter((caption) => caption.anchorClipEntityId === clip.entityId).map((caption) => caption.clipEntityId) : []);
|
|
3207
|
-
|
|
3258
|
+
const speech = {
|
|
3208
3259
|
id: clip.entityId,
|
|
3209
3260
|
kind: "speech",
|
|
3210
3261
|
media_duration_ms: placement.duration,
|
|
3211
3262
|
audio_storage_key: storageKey(asset),
|
|
3212
3263
|
origin_speech_id: external.key,
|
|
3264
|
+
origin_asset_system: external.system,
|
|
3213
3265
|
audio_script: assembled?.text ?? (graph.adjacent(content.entityId, "audio-script-source").length > 0 ? assembleScriptText(graph.one(content.entityId, "audio-script-source", "audio-script")) : ""),
|
|
3214
3266
|
volume: volume.data,
|
|
3215
3267
|
voice: object(voice) ? {
|
|
@@ -3217,7 +3269,8 @@ function projectEntityTimeline(rows) {
|
|
|
3217
3269
|
name: typeof voice.name === "string" ? voice.name : void 0
|
|
3218
3270
|
} : void 0,
|
|
3219
3271
|
caption_ids: captionIds
|
|
3220
|
-
}
|
|
3272
|
+
};
|
|
3273
|
+
parts[clip.entityId] = { speech };
|
|
3221
3274
|
} else if (track.role === "caption") {
|
|
3222
3275
|
const assembled = assembleCaptionContent(rows, content.entityId);
|
|
3223
3276
|
const text = assembled.text;
|
|
@@ -3297,7 +3350,10 @@ function storageKey(asset) {
|
|
|
3297
3350
|
function externalLocator(asset, system) {
|
|
3298
3351
|
const { system: found, key } = asset.payload;
|
|
3299
3352
|
if (found !== system || typeof key !== "string" || key.trim() === "") fail("Asset " + asset.entityId + " requires an explicit " + system + " key");
|
|
3300
|
-
return {
|
|
3353
|
+
return {
|
|
3354
|
+
key,
|
|
3355
|
+
system
|
|
3356
|
+
};
|
|
3301
3357
|
}
|
|
3302
3358
|
function captionStyle(value) {
|
|
3303
3359
|
if (value === void 0) return void 0;
|
|
@@ -3750,6 +3806,19 @@ function writeEntityOrder(doc, group, roots, token) {
|
|
|
3750
3806
|
//#region src/entity-crdt/entity-document.ts
|
|
3751
3807
|
const LORO_ENTITY_SCHEMA = "medeo.entities.loro.v1";
|
|
3752
3808
|
const MATERIALIZE_ORIGIN = "medeo.materialize";
|
|
3809
|
+
/** Read persisted relation roots before current entity versions are projected. */
|
|
3810
|
+
function readEntityRelationRecords(doc) {
|
|
3811
|
+
if (root(doc).get("schema") !== "medeo.entities.loro.v1") throw new Error("Document has no Loro entity state");
|
|
3812
|
+
return relationRecords(doc).map(({ rootId, identity, map, deleted }) => ({
|
|
3813
|
+
relationId: createRelationId(rootId),
|
|
3814
|
+
relationKind: identity.kind,
|
|
3815
|
+
endpoint0EntityId: createEntityId(identity.endpoints[0]),
|
|
3816
|
+
endpoint1EntityId: createEntityId(identity.endpoints[1]),
|
|
3817
|
+
metadata: requireMap(map, "metadata").toJSON(),
|
|
3818
|
+
trace: requireMap(map, "trace").toJSON(),
|
|
3819
|
+
deleted
|
|
3820
|
+
}));
|
|
3821
|
+
}
|
|
3753
3822
|
function hash(value) {
|
|
3754
3823
|
return bytesToHex(sha256(new TextEncoder().encode(canonicalJson(value))));
|
|
3755
3824
|
}
|
|
@@ -3841,23 +3910,17 @@ function readEntityDocument(doc, allowSelectionConflicts = false) {
|
|
|
3841
3910
|
}
|
|
3842
3911
|
};
|
|
3843
3912
|
});
|
|
3844
|
-
const relations =
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
if (original.endpoints.some((id) => !ids.has(family(doc, id)))) continue;
|
|
3850
|
-
const endpoints = original.endpoints.map((id) => currentId(doc, id, ids));
|
|
3851
|
-
const relationId = equalJson(endpoints, original.endpoints) ? key : `relation_${hash([key, endpoints])}`;
|
|
3852
|
-
relations.push({
|
|
3853
|
-
relationId: createRelationId(relationId),
|
|
3854
|
-
relationKind: original.kind,
|
|
3913
|
+
const relations = activeRelationRecords(doc, ids).map(({ rootId, identity, map }) => {
|
|
3914
|
+
const endpoints = identity.endpoints.map((id) => currentId(doc, id, ids));
|
|
3915
|
+
return {
|
|
3916
|
+
relationId: createRelationId(equalJson(endpoints, identity.endpoints) ? rootId : `relation_${hash([rootId, endpoints])}`),
|
|
3917
|
+
relationKind: identity.kind,
|
|
3855
3918
|
endpoint0EntityId: createEntityId(endpoints[0]),
|
|
3856
3919
|
endpoint1EntityId: createEntityId(endpoints[1]),
|
|
3857
3920
|
metadata: requireMap(map, "metadata").toJSON(),
|
|
3858
3921
|
trace: requireMap(map, "trace").toJSON()
|
|
3859
|
-
}
|
|
3860
|
-
}
|
|
3922
|
+
};
|
|
3923
|
+
});
|
|
3861
3924
|
const project = {};
|
|
3862
3925
|
for (const [key, value] of Object.entries(section(doc, "attachments").toJSON())) {
|
|
3863
3926
|
if (typeof value !== "string") throw new Error(`Invalid project attachment ${key}`);
|
|
@@ -3876,6 +3939,32 @@ function readEntityDocument(doc, allowSelectionConflicts = false) {
|
|
|
3876
3939
|
}
|
|
3877
3940
|
return state;
|
|
3878
3941
|
}
|
|
3942
|
+
function relationRecords(doc) {
|
|
3943
|
+
const records = [];
|
|
3944
|
+
for (const rootId of section(doc, "relations").keys().sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
3945
|
+
const map = section(doc, "relations").get(rootId);
|
|
3946
|
+
if (!(map instanceof LoroMap)) throw new Error(`Invalid relation record ${rootId}`);
|
|
3947
|
+
const value = map.get("identity");
|
|
3948
|
+
if (!isJsonObject(value) || typeof value.kind !== "string" || !Array.isArray(value.endpoints) || value.endpoints.length !== 2 || !value.endpoints.every((endpoint) => typeof endpoint === "string")) throw new Error(`Invalid relation identity ${rootId}`);
|
|
3949
|
+
const identity = {
|
|
3950
|
+
kind: value.kind,
|
|
3951
|
+
endpoints: [value.endpoints[0], value.endpoints[1]]
|
|
3952
|
+
};
|
|
3953
|
+
records.push({
|
|
3954
|
+
rootId,
|
|
3955
|
+
identity,
|
|
3956
|
+
map,
|
|
3957
|
+
deleted: map.get("deleted") === true
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
return records;
|
|
3961
|
+
}
|
|
3962
|
+
function activeRelationRecords(doc, ids) {
|
|
3963
|
+
return relationRecords(doc).filter(({ identity, deleted }) => {
|
|
3964
|
+
if (deleted) return false;
|
|
3965
|
+
return identity.endpoints.every((id) => ids.has(family(doc, id)));
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3879
3968
|
/** Native editing containers survive domain version changes and merge independently. */
|
|
3880
3969
|
var LoroEntityDocument = class LoroEntityDocument {
|
|
3881
3970
|
doc;
|
|
@@ -4106,16 +4195,47 @@ var LoroEntityDraft = class {
|
|
|
4106
4195
|
});
|
|
4107
4196
|
}
|
|
4108
4197
|
link(row) {
|
|
4198
|
+
if (row.relationKind === "caption-alignment") throw new Error("caption-alignment Relations are read-only legacy facts");
|
|
4199
|
+
if (row.relationKind === "generated" || row.relationKind === "clip-anchor") throw new Error(`Author ordered ${row.relationKind} Relations with the dedicated role-named method`);
|
|
4109
4200
|
this.record({
|
|
4110
4201
|
type: "relation",
|
|
4111
4202
|
row: clone(row)
|
|
4112
4203
|
});
|
|
4113
4204
|
}
|
|
4114
|
-
|
|
4205
|
+
linkGenerated(input) {
|
|
4206
|
+
this.record({
|
|
4207
|
+
type: "relation",
|
|
4208
|
+
row: {
|
|
4209
|
+
relationId: input.relationId,
|
|
4210
|
+
relationKind: "generated",
|
|
4211
|
+
endpoint0EntityId: input.outputEntityId,
|
|
4212
|
+
endpoint1EntityId: input.inputEntityId,
|
|
4213
|
+
metadata: {},
|
|
4214
|
+
trace: clone(input.trace ?? {})
|
|
4215
|
+
}
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
linkClipAnchor(input) {
|
|
4219
|
+
this.record({
|
|
4220
|
+
type: "relation",
|
|
4221
|
+
row: {
|
|
4222
|
+
relationId: input.relationId,
|
|
4223
|
+
relationKind: "clip-anchor",
|
|
4224
|
+
endpoint0EntityId: input.childClipEntityId,
|
|
4225
|
+
endpoint1EntityId: input.hostClipEntityId,
|
|
4226
|
+
metadata: {},
|
|
4227
|
+
trace: clone(input.trace ?? {})
|
|
4228
|
+
}
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
patchRelationTrace(relationId, trace) {
|
|
4232
|
+
const relation = this.snapshot().rows.relations.find((row) => row.relationId === relationId);
|
|
4233
|
+
if (relation == null) throw new Error(`Unknown relation ${relationId}`);
|
|
4234
|
+
if (relation.relationKind === "caption-alignment") throw new Error("caption-alignment Relations are read-only legacy facts");
|
|
4115
4235
|
this.record({
|
|
4116
4236
|
type: "relation-fields",
|
|
4117
4237
|
root: this.relationRoot(relationId),
|
|
4118
|
-
metadata: clone(metadata),
|
|
4238
|
+
metadata: clone(relation.metadata),
|
|
4119
4239
|
trace: clone(trace)
|
|
4120
4240
|
});
|
|
4121
4241
|
}
|
|
@@ -4211,8 +4331,9 @@ var LoroEntityDraft = class {
|
|
|
4211
4331
|
const key = this.relationRoot(relationId);
|
|
4212
4332
|
const map = section(this.doc, "relations").get(key);
|
|
4213
4333
|
if (!(map instanceof LoroMap) || map.get("deleted") === true) throw new Error("Unknown relation");
|
|
4334
|
+
if (map.get("identity").kind === "caption-alignment") throw new Error("caption-alignment Relations are read-only legacy facts");
|
|
4214
4335
|
for (const change of changes) {
|
|
4215
|
-
if (
|
|
4336
|
+
if (change.path[0] !== "trace" || change.path.length < 2) throw new Error("Relation changes may only edit trace fields");
|
|
4216
4337
|
const payload = {
|
|
4217
4338
|
metadata: requireMap(map, "metadata").toJSON(),
|
|
4218
4339
|
trace: requireMap(map, "trace").toJSON()
|
|
@@ -4728,4 +4849,4 @@ function createPlainMemoryAdapter(document, options) {
|
|
|
4728
4849
|
return new PlainMemoryAdapter(document, options);
|
|
4729
4850
|
}
|
|
4730
4851
|
//#endregion
|
|
4731
|
-
export {
|
|
4852
|
+
export { VideoDocumentValidationError as $, resolveEntityTimelineLayout as A, selectAudioScriptSegment as B, canonicalJson as C, effectiveVideoClipDurationMs as Ct, resolveFieldPath as D, videoDocumentMirrorSchema as Dt, assertFieldChanges as E, recordEntries as Et, assembleCaptionContent as F, createRelationId as G, variantBaseEntityIds as H, assembleEntityContent as I, buildInitialVideoDocument as J, hasSequence as K, assemblePhoneticScriptContent as L, validateEntityRelationTransition as M, decodeEntityRelationRows as N, projectEntityTimeline as O, base64ToBytes as Ot, ScriptCompositionError as P, toVideoDocument as Q, assembleScriptText as R, scriptSourceAssetOf as S, DEFAULT_UNIT_TIME_MS as St, applyFieldChanges as T, partUnionToDraft as Tt, isJsonObject as U, updateEntityFields as V, createEntityId as W, derivePositionFromAbs as X, buildSpeechHostMap as Y, fromVideoDocument as Z, audioScriptAssetHash as _, syncAggregatedClipsTimePosition as _t, createMirrorVideoDocument as a, ensureLaneTrack as at, phoneticScriptAssetHash as b, partDurationMs as bt, DocumentMutationGuard as c, laneTrackId as ct, LoroEntityDraft as d, arrangeMainTrackSeamlessly as dt, assertValidVideoDocument as et, readEntityDocument as f, fillMainTrackTimeGaps as ft, audioScriptAssetFields as g, resolveSpeechOverlapByShiftingVideos as gt, audioScriptAssetContent as h, resolveAllSpeechOverlaps as ht, MirrorVideoDocumentAdapter as i, LANE_KINDS_IN_STACK_ORDER as it, EntityTimelineProjectionError as j, EntityProjectionGraph as k, bytesToBase64 as kt, LORO_ENTITY_SCHEMA as l, solveVideoDocument as lt, entityOrderGroups as m, recalculateTimelineDuration as mt, createPlainMemoryAdapter as n, partUnionSchema as nt, createMirrorVideoDocumentAdapter as o, findLaneTrack as ot, readEntityRelationRecords as p, reassignSpeechesToVideoClipsByTime as pt, isMediaAssetVariantKind as q, generatePartId as r, videoDocumentSchema as rt, readVideoDocumentFromDraft as s, hasResolvedPlacement as st, PlainMemoryAdapter as t, validateVideoDocument as tt, LoroEntityDocument as u, cascadeAfterVideoClipChanges as ut, audioScriptAssetOf as v, TIMELINE_SKELETON_DURATION_MS as vt, equalJson as w, speedOf as wt, phoneticScriptAssetOf as x, safeDurationMs as xt, phoneticScriptAssetContent as y, isEmptyVideoClip as yt, findComposedAudioScript as z };
|