@mengine/medeo-tool 2.0.1-alpha.7 → 2.0.1-alpha.9

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.
@@ -8,8 +8,8 @@ interface JsonObject {
8
8
  }
9
9
  type KnownEntityKind = 'axvideo' | 'timeline' | 'track' | 'clip' | 'asset' | 'video' | 'audio' | 'voice' | 'image' | 'sequence-marker' | 'viewport' | 'audio-script' | 'phonetic-script' | 'caption';
10
10
  /** Asset identity, either an old physical-only row or a directly composed media variant. */
11
- type ResourceEntityKind = 'image' | 'video' | 'audio' | 'voice';
12
- type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'physical-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'audio-script-source' | 'audio-script-marker';
11
+ type ResourceEntityKind = 'image' | 'video' | 'audio';
12
+ type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'physical-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'voice-timbre' | 'audio-script-source' | 'audio-script-marker';
13
13
  type AuthorableRelationKind = KnownRelationKind;
14
14
  interface BoundedNativeSequencePayload extends JsonObject {
15
15
  /** Factual coordinates from recalled media metadata; never invent an end/duration. */
@@ -50,6 +50,14 @@ type MediaAssetPayload = JsonObject & {
50
50
  };
51
51
  storageKey?: string;
52
52
  };
53
+ /** The voice-library timbre a synthesized Audio was rendered with. */
54
+ type VoiceIdentityPayload = JsonObject & {
55
+ voice: {
56
+ system: 'voice-library';
57
+ key: string;
58
+ name?: string;
59
+ };
60
+ };
53
61
  type CaptionTextSelection = JsonObject & {
54
62
  segmentId: string; /** Half-open Unicode code-point range within the selected source segment. */
55
63
  textRange?: {
@@ -68,8 +76,10 @@ interface EntityPayloadByKind {
68
76
  /** Physical resource fields; never a copy of Caption content. */
69
77
  asset: JsonObject;
70
78
  video: BoundedNativeSequencePayload & MediaAssetPayload;
79
+ /** Every playable sound, including video original audio and synthesized voiceovers. */
71
80
  audio: BoundedNativeSequencePayload & MediaAssetPayload;
72
- voice: BoundedNativeSequencePayload & MediaAssetPayload;
81
+ /** A timbre identity, never playable content; the rendered take is an `audio` entity. */
82
+ voice: VoiceIdentityPayload;
73
83
  image: UnboundedConstantSequencePayload & MediaAssetPayload;
74
84
  'sequence-marker': JsonObject & {
75
85
  sourceRange: {
@@ -115,8 +125,15 @@ interface EntityPayloadByKind {
115
125
  }[];
116
126
  };
117
127
  }
118
- /** Stored own fields; a variant may obtain required content fields from its declared bases. */
119
- type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (K extends 'caption' | 'voice' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never) | (JsonObject & Partial<EntityPayloadByKind[K]> & {
128
+ /**
129
+ * Stored own fields; a variant may obtain required content fields from its
130
+ * declared bases.
131
+ *
132
+ * Caption and Audio may also be declared resource-only: the ASR transcript and
133
+ * the synthesized voiceover are attached by the host, which then fills in the
134
+ * remaining factual fields.
135
+ */
136
+ type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (K extends 'caption' | 'audio' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never) | (JsonObject & Partial<EntityPayloadByKind[K]> & {
120
137
  baseEntityIds: string[];
121
138
  });
122
139
  interface SandboxEntity<K extends KnownEntityKind = KnownEntityKind> {
@@ -238,4 +255,4 @@ interface BusinessRelationFacade {
238
255
  }
239
256
  //#endregion
240
257
  export { UpdateEntityInput as C, UnlinkRelationInput as S, LinkRelationInput as _, DeleteEntityInput as a, SandboxEntity as b, EntitySandboxCheckpoint as c, JsonObject as d, JsonPrimitive as f, LinkGeneratedRelationInput as g, KnownRelationKind as h, CreateEntityInput as i, EntityStoreSnapshot as l, KnownEntityKind as m, BusinessEntityFacade as n, EntityCommand as o, JsonValue as p, BusinessRelationFacade as r, EntityPlanState as s, AuthorableRelationKind as t, EntityUpdateInput as u, RelationUpdateInput as v, SandboxRelation as x, ResourceEntityKind as y };
241
- //# sourceMappingURL=entity-contract-G4HxwNXl.d.mts.map
258
+ //# sourceMappingURL=entity-contract-Bpe6NRIQ.d.mts.map
@@ -1,7 +1,84 @@
1
1
  import { applyFieldChanges, assertCanonicalEditorResources, assertFieldChanges, ensureEditorFoundation, importMediaAsset, readDocumentAudioScript } from "@mengine/medeo-client";
2
2
  //#region ../medeo-dsl/src/entities.ts
3
+ /**
4
+ * What every known entity kind declares.
5
+ *
6
+ * `kinds` is a multi-declaration: every entry holds at the same time, the first
7
+ * naming the object and the rest the properties it shares with others. The
8
+ * order carries no priority, endpoint role, or edit sequence — an operation
9
+ * asks whether the entity declares the kind it needs, never what its "main
10
+ * type" is.
11
+ *
12
+ * `Caption` declares `Sequence` on purpose: it owns the display timing of the
13
+ * one AudioScript segment it shows, which is what admits it into a Clip.
14
+ * `Voice` declares neither `MediaFile` nor `Sequence` — it is a timbre
15
+ * identity, not playable content; a rendered voiceover is an `audio` entity.
16
+ */
17
+ const ENTITY_KINDS = Object.freeze({
18
+ axvideo: Object.freeze([
19
+ "AXVideo",
20
+ "Container",
21
+ "Sequence",
22
+ "Visual",
23
+ "Audible"
24
+ ]),
25
+ timeline: Object.freeze(["Timeline", "Container"]),
26
+ track: Object.freeze([
27
+ "Track",
28
+ "Container",
29
+ "Sequence"
30
+ ]),
31
+ clip: Object.freeze(["Clip", "Container"]),
32
+ asset: Object.freeze(["Asset"]),
33
+ video: Object.freeze([
34
+ "Video",
35
+ "MediaFile",
36
+ "Sequence",
37
+ "Visual",
38
+ "Audible"
39
+ ]),
40
+ audio: Object.freeze([
41
+ "Audio",
42
+ "MediaFile",
43
+ "Sequence",
44
+ "Audible"
45
+ ]),
46
+ image: Object.freeze([
47
+ "Image",
48
+ "MediaFile",
49
+ "Sequence",
50
+ "Visual"
51
+ ]),
52
+ voice: Object.freeze([
53
+ "Voice",
54
+ "Container",
55
+ "IPBound"
56
+ ]),
57
+ "sequence-marker": Object.freeze(["SequenceMarker"]),
58
+ viewport: Object.freeze(["Viewport"]),
59
+ "audio-script": Object.freeze(["AudioScript", "SegmentText"]),
60
+ "phonetic-script": Object.freeze(["PhoneticScript", "SegmentText"]),
61
+ caption: Object.freeze([
62
+ "Caption",
63
+ "SegmentText",
64
+ "Sequence"
65
+ ])
66
+ });
67
+ /** What a known kind declares; extension kinds declare nothing through this table. */
68
+ function kindsOf(entityKind) {
69
+ return isKnownEntityKind(entityKind) ? ENTITY_KINDS[entityKind] : [];
70
+ }
71
+ function declaresKind(entityKind, kind) {
72
+ return kindsOf(entityKind).includes(kind);
73
+ }
74
+ /**
75
+ * Sequence admission asks `kinds` first, so an entity that does not declare
76
+ * `Sequence` can never buy its way in by carrying the fields. Extension kinds
77
+ * declare nothing and stay structurally detected.
78
+ */
3
79
  function hasSequence(entity) {
4
- if (isKnownNonSequenceKind(entity.entityKind) || isReservedEntityKind(entity.entityKind)) return false;
80
+ if (isReservedEntityKind(entity.entityKind)) return false;
81
+ if (isKnownEntityKind(entity.entityKind) && !declaresKind(entity.entityKind, "Sequence")) return false;
5
82
  return isSequenceFields(entity);
6
83
  }
7
84
  function isSequenceFields(value) {
@@ -14,22 +91,16 @@ function isSequenceFields(value) {
14
91
  if (value.sampling !== "native" && value.sampling !== "constant" && value.sampling !== "derived") return false;
15
92
  return "coordinateSpace" in value && value.coordinateSpace !== void 0;
16
93
  }
17
- function isKnownSequenceKind(kind) {
18
- return isMediaAssetVariantKind(kind) || kind === "caption" || kind === "axvideo";
19
- }
20
94
  function isMediaAssetVariantKind(kind) {
21
- return kind === "video" || kind === "image" || kind === "audio" || kind === "voice";
95
+ return kind === "video" || kind === "image" || kind === "audio";
22
96
  }
23
97
  function isKnownEntityKind(kind) {
24
- return isKnownSequenceKind(kind) || isKnownNonSequenceKind(kind);
98
+ return Object.hasOwn(ENTITY_KINDS, kind);
25
99
  }
26
100
  function isReservedEntityKind(kind) {
27
101
  const normalized = kind.toLowerCase().replaceAll("-", "").replaceAll("_", "");
28
102
  return normalized === "speech" || normalized === "videodocument";
29
103
  }
30
- function isKnownNonSequenceKind(kind) {
31
- return kind === "timeline" || kind === "track" || kind === "clip" || kind === "asset" || kind === "sequence-marker" || kind === "viewport" || kind === "audio-script" || kind === "phonetic-script";
32
- }
33
104
  function isRecord$1(value) {
34
105
  return typeof value === "object" && value != null && !Array.isArray(value);
35
106
  }
@@ -209,14 +280,17 @@ function assembleCaptionContent(rows, captionEntityId) {
209
280
  requireEntityKind(rows, captionEntityId, "caption");
210
281
  const caption = assembleEntityContent(rows, captionEntityId);
211
282
  const script = findComposedAudioScript(rows, captionEntityId, "caption");
212
- const segment = selectAudioScriptSegment({
283
+ const selection = captionSelection(caption);
284
+ const composed = {
213
285
  ...script,
214
286
  payload: caption.payload
215
- }, captionSelection(caption));
287
+ };
288
+ const segment = selectAudioScriptSegment(composed, selection);
216
289
  return {
217
290
  caption,
218
291
  audioScript: script,
219
292
  segments: [segment],
293
+ segmentIndex: scriptSegments(composed).findIndex((entry) => entry.segmentId === selection.segmentId),
220
294
  text: segment.text
221
295
  };
222
296
  }
@@ -608,12 +682,15 @@ function validateKnownEntityPayload(entity) {
608
682
  break;
609
683
  case "video":
610
684
  case "audio":
611
- case "voice":
685
+ validateSequencePayload(value, "bounded", "native", problems);
686
+ validateMediaAssetFields(value, problems);
687
+ break;
612
688
  case "caption":
613
689
  validateSequencePayload(value, "bounded", "native", problems);
614
- if (entity.entityKind !== "caption") validateMediaAssetFields(value, problems);
615
- if (entity.entityKind === "voice") validateVoicePayload(value, problems);
616
- if (entity.entityKind === "caption") validateCaptionPayload(value, problems);
690
+ validateCaptionPayload(value, problems);
691
+ break;
692
+ case "voice":
693
+ validateVoicePayload(value, problems);
617
694
  break;
618
695
  case "image":
619
696
  validateSequencePayload(value, "unbounded", "constant", problems);
@@ -720,11 +797,26 @@ function validateExternalLocator(value, problems) {
720
797
  function validateStorageKey(value, problems) {
721
798
  if (value.storageKey !== void 0 && (typeof value.storageKey !== "string" || value.storageKey.trim() === "")) problems.push("storageKey must be a non-empty string when present");
722
799
  }
800
+ /**
801
+ * Voice is a timbre identity, not playable content: it owns the voice-library
802
+ * descriptor and nothing that would make it look like media or a Sequence. The
803
+ * rendered voiceover is an `audio` entity bound by a `voice-timbre` Relation.
804
+ */
723
805
  function validateVoicePayload(value, problems) {
806
+ for (const key of [
807
+ "extent",
808
+ "sampling",
809
+ "coordinateSpace",
810
+ "external",
811
+ "storageKey"
812
+ ]) if (Object.hasOwn(value, key)) problems.push(`${key} belongs to the rendered Audio, not to the Voice identity`);
724
813
  const voice = value.voice;
725
- if (voice === void 0) return;
814
+ if (voice === void 0) {
815
+ problems.push("voice is required; a Voice entity is its voice-library identity");
816
+ return;
817
+ }
726
818
  if (!isRecord(voice)) {
727
- problems.push("voice must be an object when present");
819
+ problems.push("voice must be an object");
728
820
  return;
729
821
  }
730
822
  if (voice.system !== "voice-library") problems.push("voice.system must be \"voice-library\"");
@@ -856,7 +948,6 @@ function isOwnedLocalEntityValuePath(entityKind, path) {
856
948
  if ([
857
949
  "video",
858
950
  "audio",
859
- "voice",
860
951
  "image",
861
952
  "caption",
862
953
  "axvideo"
@@ -920,7 +1011,7 @@ function collectRelatedEntities(entityRefs, index) {
920
1011
  };
921
1012
  }
922
1013
  function expectedSequenceShape(kind) {
923
- if (kind === "video" || kind === "audio" || kind === "voice" || kind === "caption") return {
1014
+ if (kind === "video" || kind === "audio" || kind === "caption") return {
924
1015
  extent: "bounded",
925
1016
  sampling: "native"
926
1017
  };
@@ -961,7 +1052,7 @@ const generatedRelationSpec = Object.freeze({
961
1052
  });
962
1053
  const captionAlignmentRelationSpec = Object.freeze({
963
1054
  kind: "caption-alignment",
964
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio", "voice"])),
1055
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["caption"]), new Set(["audio"])),
965
1056
  validateMetadata: isCaptionAlignmentMetadata
966
1057
  });
967
1058
  /** `clip-anchor(child, host)` means endpoint 0 follows endpoint 1. */
@@ -970,20 +1061,29 @@ const clipAnchorRelationSpec = Object.freeze({
970
1061
  validateEndpoints: (endpoints) => endpoints[0].current().entityKind === "clip" && endpoints[1].current().entityKind === "clip",
971
1062
  validateMetadata: isEmptyMetadata
972
1063
  });
973
- /** Voice is generated from PhoneticScript; kinds determine roles regardless of endpoint positions. */
1064
+ /**
1065
+ * The rendered voiceover Audio and the PhoneticScript it was synthesized from;
1066
+ * kinds determine roles regardless of endpoint positions.
1067
+ */
974
1068
  const phoneticScriptRenderRelationSpec = Object.freeze({
975
1069
  kind: "phonetic-script-render",
976
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["voice"]), new Set(["phonetic-script"])),
1070
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio"]), new Set(["phonetic-script"])),
1071
+ validateMetadata: isEmptyMetadata
1072
+ });
1073
+ /**
1074
+ * The timbre identity a rendered voiceover Audio was synthesized with. Voice
1075
+ * stays an identity: it never carries the audio itself, so the link between the
1076
+ * two is a Relation rather than one shared entity.
1077
+ */
1078
+ const voiceTimbreRelationSpec = Object.freeze({
1079
+ kind: "voice-timbre",
1080
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio"]), new Set(["voice"])),
977
1081
  validateMetadata: isEmptyMetadata
978
1082
  });
979
1083
  /** AudioScript was transcribed from Audio, Video, or recorded Voice; kinds determine roles. */
980
1084
  const audioScriptSourceRelationSpec = Object.freeze({
981
1085
  kind: "audio-script-source",
982
- validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio-script"]), new Set([
983
- "audio",
984
- "video",
985
- "voice"
986
- ])),
1086
+ validateEndpoints: (endpoints) => hasKinds(endpoints, new Set(["audio-script"]), new Set(["audio", "video"])),
987
1087
  validateMetadata: isEmptyMetadata
988
1088
  });
989
1089
  /**
@@ -1010,6 +1110,7 @@ const builtInRelationSpecs = Object.freeze([
1010
1110
  captionAlignmentRelationSpec,
1011
1111
  clipAnchorRelationSpec,
1012
1112
  phoneticScriptRenderRelationSpec,
1113
+ voiceTimbreRelationSpec,
1013
1114
  audioScriptSourceRelationSpec,
1014
1115
  audioScriptMarkerRelationSpec
1015
1116
  ]);
@@ -1039,7 +1140,7 @@ function hasAssetAndSequence(endpoints) {
1039
1140
  return first.entityKind === "asset" && hasSequence(second) || second.entityKind === "asset" && hasSequence(first);
1040
1141
  }
1041
1142
  function isGeneratedMedia(entity) {
1042
- return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio" || entity.entityKind === "voice";
1143
+ return entity.entityKind === "video" || entity.entityKind === "image" || entity.entityKind === "audio";
1043
1144
  }
1044
1145
  function isEmptyMetadata(value) {
1045
1146
  return isJsonObject(value) && Object.keys(value).length === 0;
@@ -1599,6 +1700,7 @@ var EntitySandbox = class {
1599
1700
  const assembled = assembleCaptionContent(toDslRows(this.state), createEntityId(entityId));
1600
1701
  return clone({
1601
1702
  audio_script_entity_id: assembled.audioScript.entityId,
1703
+ segment_index: assembled.segmentIndex,
1602
1704
  text: assembled.text,
1603
1705
  segments: assembled.segments.map((segment) => ({ ...segment }))
1604
1706
  });
@@ -1788,7 +1890,7 @@ var EntitySandbox = class {
1788
1890
  }
1789
1891
  for (const row of rows.relations) {
1790
1892
  if (this.state.relations.some((relation) => relation.relation_id === row.relationId)) continue;
1791
- if (row.relationKind !== "timeline-track") throw new Error(`Unexpected resource Relation ${row.relationKind}`);
1893
+ if (row.relationKind !== "timeline-track" && row.relationKind !== "voice-timbre") throw new Error(`Unexpected resource Relation ${row.relationKind}`);
1792
1894
  this.link({
1793
1895
  relation_id: row.relationId,
1794
1896
  relation_kind: row.relationKind,
@@ -2095,4 +2197,4 @@ const numericMarkerComparators = { compareMarkerPoints: (_marker, _range, left,
2095
2197
  //#endregion
2096
2198
  export { isJsonObject as a, isMediaAssetVariantKind as c, businessState as i, toDslRows as n, createEntityId as o, businessFacades as r, createRelationId as s, EntitySandbox as t };
2097
2199
 
2098
- //# sourceMappingURL=entity-sandbox-DSFbfybl.mjs.map
2200
+ //# sourceMappingURL=entity-sandbox-CJeLcNcx.mjs.map