@cueframe/kernel 0.2.52 → 0.2.53
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/_members/composition/fonts/bundledFaces.d.ts +9 -15
- package/dist/_members/composition/fonts/captionFaces.d.ts +2 -3
- package/dist/_members/composition/ops/captionOps.d.ts +11 -0
- package/dist/_members/composition/ops/compositionOps.d.ts +20 -19
- package/dist/_members/composition/validators/composition.d.ts +644 -0
- package/dist/_members/composition/validators/registry.d.ts +1 -1
- package/dist/_members/composition/validators/render.d.ts +292 -0
- package/dist/_members/composition-ops/index.d.ts +74 -0
- package/dist/_members/core/render/compositionAudioAndDepth.d.ts +1 -1
- package/dist/_members/core/render/index.d.ts +1 -1
- package/dist/_members/core/render/types.d.ts +3 -1
- package/dist/_members/plane/contract.d.ts +1 -1
- package/dist/_members/primitives/captions/Captions.d.ts +18 -1
- package/dist/_members/primitives/captions/index.d.ts +1 -1
- package/dist/animate.js +2 -2
- package/dist/authoring/build-tools.js +6 -6
- package/dist/authoring/context.js +5 -5
- package/dist/authoring/tools.js +5 -5
- package/dist/authoring.js +9 -9
- package/dist/{chunk-MYBK6DKF.js → chunk-2BVKNYRB.js} +1 -1
- package/dist/{chunk-2M65KQNG.js → chunk-46DGDPXR.js} +296 -47
- package/dist/{chunk-KMDZDH5E.js → chunk-4DM63RTI.js} +4 -4
- package/dist/{chunk-QVBUYWW6.js → chunk-5E4YP44R.js} +1 -1
- package/dist/{chunk-3OG2674K.js → chunk-7AUHC2X7.js} +1 -1
- package/dist/{chunk-AVTEPAWI.js → chunk-7RGDRDAR.js} +17 -13
- package/dist/{chunk-7XZW26U2.js → chunk-AONWKKXK.js} +1 -1
- package/dist/{chunk-4B4V3HYW.js → chunk-C6ONUJIO.js} +1 -1
- package/dist/{chunk-MBSQKRDF.js → chunk-CJMUTCJW.js} +2 -2
- package/dist/{chunk-CZOVEX2D.js → chunk-CR3G2JQP.js} +1 -1
- package/dist/{chunk-2PU2TSUG.js → chunk-D5E5XFHK.js} +1 -1
- package/dist/{chunk-6WQHVYJD.js → chunk-G7ZSFSQG.js} +1 -1
- package/dist/{chunk-5ZQLMPDY.js → chunk-GAACUQAB.js} +1 -1
- package/dist/{chunk-3I43W7DP.js → chunk-LPUQTBKU.js} +41 -20
- package/dist/{chunk-WZ22ZYXD.js → chunk-P3DYA5NA.js} +4 -4
- package/dist/{chunk-Z6NY6MCL.js → chunk-RMLGMYEJ.js} +1 -1
- package/dist/{chunk-5JCLZPMV.js → chunk-TPKPU4PE.js} +56 -38
- package/dist/{chunk-CV3QQUMK.js → chunk-UFNIB3JL.js} +117 -4
- package/dist/{chunk-ACURZCPN.js → chunk-UKKY7HWN.js} +1 -1
- package/dist/{chunk-IHXMBXXL.js → chunk-VTM7NEJC.js} +30 -4
- package/dist/{chunk-JWW54N24.js → chunk-ZFW35DLG.js} +1 -1
- package/dist/{chunk-FSEB4LSU.js → chunk-ZIPGDGBR.js} +1 -1
- package/dist/component-runtime/render.js +2 -2
- package/dist/component-runtime.js +3 -3
- package/dist/compose.js +5 -5
- package/dist/composition/fonts.js +1 -1
- package/dist/composition/graphics-plan.js +3 -3
- package/dist/composition/ops.js +4 -4
- package/dist/composition/projection.js +3 -3
- package/dist/composition/validators.js +13 -3
- package/dist/composition-ops.js +8 -8
- package/dist/composition.js +15 -5
- package/dist/{context-2MBCDTCZ.js → context-UAS5WJRB.js} +5 -5
- package/dist/core/render.js +4 -4
- package/dist/core.js +4 -4
- package/dist/mcp/adapter.js +2 -2
- package/dist/mcp/core.js +1 -1
- package/dist/mcp/scene-shot.js +2 -2
- package/dist/mcp/server.js +12 -12
- package/dist/mcp.js +12 -12
- package/dist/primitives/captions.js +195 -32
- package/dist/primitives/effects.js +3 -3
- package/dist/primitives/layouts.js +3 -3
- package/dist/primitives/scenes.js +3 -3
- package/dist/primitives.js +5 -5
- package/dist/render-harness/cardFonts.js +2 -2
- package/dist/render-harness.js +7 -7
- package/package.json +14 -14
|
@@ -75,7 +75,7 @@ var BUNDLED_FONT_FACES = Object.freeze({
|
|
|
75
75
|
},
|
|
76
76
|
"Instrument Serif": {
|
|
77
77
|
slug: "instrument-serif",
|
|
78
|
-
faces: [{ weight: "400", file: "400" }],
|
|
78
|
+
faces: [{ weight: "400", file: "400" }, { weight: "400", file: "400-italic", style: "italic" }],
|
|
79
79
|
captionDefaults: { base: 400, emphasis: 400 }
|
|
80
80
|
},
|
|
81
81
|
"National Park": {
|
|
@@ -102,19 +102,19 @@ function bundledFaceCoversWeight(face, weight) {
|
|
|
102
102
|
return weight >= lo && weight <= hi;
|
|
103
103
|
}
|
|
104
104
|
function bundledFontFaceExists(family, weight, style) {
|
|
105
|
-
if (style !== BUNDLED_FONT_STYLE) return false;
|
|
106
105
|
const bundled = bundledFontFamilyFaces(family);
|
|
107
106
|
if (!bundled) return false;
|
|
108
|
-
return bundled.faces.some((face) => bundledFaceCoversWeight(face, weight));
|
|
107
|
+
return bundled.faces.some((face) => (face.style ?? BUNDLED_FONT_STYLE) === style && bundledFaceCoversWeight(face, weight));
|
|
109
108
|
}
|
|
110
109
|
function describeBundledFontWeights(family) {
|
|
111
110
|
const bundled = bundledFontFamilyFaces(family);
|
|
112
111
|
if (!bundled) return "";
|
|
113
|
-
return bundled.faces.map((face) => face.weight.split(/\s+/).join("-")).join(", ");
|
|
112
|
+
return [...new Set(bundled.faces.map((face) => face.weight.split(/\s+/).join("-")))].join(", ");
|
|
114
113
|
}
|
|
115
114
|
function unrenderableFaceField(family, weight, style, sealed) {
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
const bundled = bundledFontFamilyFaces(family);
|
|
116
|
+
if (bundled) {
|
|
117
|
+
return bundled.faces.some((face) => (face.style ?? BUNDLED_FONT_STYLE) === style) ? "fontWeight" : "fontStyle";
|
|
118
118
|
}
|
|
119
119
|
if (sealed.hasFamily(family)) {
|
|
120
120
|
return sealed.hasWeight(family, weight) ? "fontStyle" : "fontWeight";
|
|
@@ -165,7 +165,7 @@ function admissibleFamilyForStack(stack) {
|
|
|
165
165
|
return resolved.kind === "bundled" ? resolved.family : stack;
|
|
166
166
|
}
|
|
167
167
|
function weightRanges(faces) {
|
|
168
|
-
return faces.map((face) => {
|
|
168
|
+
return [...new Map(faces.map((face) => [face.weight, face])).values()].map((face) => {
|
|
169
169
|
const parts = face.weight.split(/\s+/).map(Number);
|
|
170
170
|
const min = parts[0];
|
|
171
171
|
const max = parts.length > 1 ? parts[1] : min;
|
|
@@ -188,7 +188,7 @@ function vendoredFontCatalog() {
|
|
|
188
188
|
return {
|
|
189
189
|
family,
|
|
190
190
|
weights: weightRanges(entry.faces),
|
|
191
|
-
styles: [BUNDLED_FONT_STYLE],
|
|
191
|
+
styles: [...new Set(entry.faces.map((face) => face.style ?? BUNDLED_FONT_STYLE))],
|
|
192
192
|
captionDefaults: entry.captionDefaults,
|
|
193
193
|
origin: aliased.has(family) ? "aliased" : "bundled"
|
|
194
194
|
};
|
|
@@ -261,12 +261,12 @@ function faceIssue(use) {
|
|
|
261
261
|
return {
|
|
262
262
|
field: use.field,
|
|
263
263
|
face: { family: use.stack, weight: use.weight, style: use.style },
|
|
264
|
-
message: `font_not_reproducible \u2014 ${because(use)}: "${use.stack}" names no font file the render bundle carries,
|
|
264
|
+
message: `font_not_reproducible \u2014 ${because(use)}: "${use.stack}" names no font file the render bundle carries, so this stack ships no italic binary to select and the browser would slant whatever upright face it matched and the render refuses it before capture. Emphasise with emphasisFontWeight, emphasisColor or emphasisScale, or name an uploaded brand font that is sealed in italic.`
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
if (use.resolved.kind !== "bundled") return null;
|
|
268
268
|
const face = { family: use.resolved.family, weight: use.weight, style: use.style };
|
|
269
|
-
if (use.style === "italic") {
|
|
269
|
+
if (use.style === "italic" && !bundledFontFamilyFaces(use.resolved.family).faces.some((face2) => face2.style === "italic")) {
|
|
270
270
|
return {
|
|
271
271
|
field: use.field,
|
|
272
272
|
face,
|
|
@@ -274,10 +274,11 @@ function faceIssue(use) {
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
if (bundledFontFaceExists(use.resolved.family, use.weight, use.style)) return null;
|
|
277
|
+
const weightField = use.role === "emphasis" ? "emphasisFontWeight" : "fontWeight";
|
|
277
278
|
return {
|
|
278
|
-
field:
|
|
279
|
+
field: weightField,
|
|
279
280
|
face,
|
|
280
|
-
message: `font_not_reproducible \u2014 ${
|
|
281
|
+
message: `font_not_reproducible \u2014 ${weightField} ${use.weight} (${use.style}) has no file behind it: ${use.resolved.family} ships ${describeBundledFontWeights(use.resolved.family)}. The render refuses a weight outside that range before capture rather than let the browser substitute a nearby file, so set ${weightField} to a weight the family covers, or pick a family whose files do.`
|
|
281
282
|
};
|
|
282
283
|
}
|
|
283
284
|
function captionFaceIssues(style) {
|
|
@@ -377,7 +378,10 @@ function captionWeightChoices(stack) {
|
|
|
377
378
|
);
|
|
378
379
|
}
|
|
379
380
|
function captionStyleChoices(stack) {
|
|
380
|
-
|
|
381
|
+
const resolved = captionStackFamily(stack);
|
|
382
|
+
if (resolved.kind === "unknown") return ["normal", "italic"];
|
|
383
|
+
if (resolved.kind !== "bundled") return ["normal"];
|
|
384
|
+
return [...new Set(bundledFontFamilyFaces(resolved.family).faces.map((face) => face.style ?? BUNDLED_FONT_STYLE))];
|
|
381
385
|
}
|
|
382
386
|
|
|
383
387
|
// ../composition/src/fonts.ts
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
Format,
|
|
5
5
|
GraphicRole,
|
|
6
6
|
OpaqueId
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UFNIB3JL.js";
|
|
8
8
|
import {
|
|
9
9
|
brandKitCaptionFaceIssues
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7RGDRDAR.js";
|
|
11
11
|
|
|
12
12
|
// ../composition/src/validators/composePlan.ts
|
|
13
13
|
import { z } from "zod";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "./chunk-W2245F6G.js";
|
|
25
25
|
import {
|
|
26
26
|
FlexLayout
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-D5E5XFHK.js";
|
|
28
28
|
|
|
29
29
|
// ../execution/primitives/src/layouts/chat-to-preview-layout/index.tsx
|
|
30
30
|
import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
} from "./chunk-VZ7TYX6Z.js";
|
|
32
32
|
import {
|
|
33
33
|
resolveBrandRef
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-RMLGMYEJ.js";
|
|
35
35
|
import {
|
|
36
36
|
SHOT_SCALE_TARGET,
|
|
37
37
|
shotScaleZoom
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
bundledFontFaceExists,
|
|
65
65
|
mergedCaptionFaceIssues,
|
|
66
66
|
unrenderableFaceField
|
|
67
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-7RGDRDAR.js";
|
|
68
68
|
|
|
69
69
|
// ../execution/core/src/render/sceneCaptureIdentity.ts
|
|
70
70
|
var INTENT_FIELDS = [
|
|
@@ -1226,22 +1226,33 @@ function bindBehindSubjectHosts(overlays, clips) {
|
|
|
1226
1226
|
);
|
|
1227
1227
|
}
|
|
1228
1228
|
}
|
|
1229
|
-
function assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, output, effects = []) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
{ candidateClipIds: window.candidateClipIds, startFrame: window.startFrame, endFrame: window.endFrame }
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1229
|
+
function assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, output, effects = [], captionLayouts = []) {
|
|
1230
|
+
const requireCoverage = (label, startMs, endMs) => {
|
|
1231
|
+
const startFrame = Math.round(startMs / 1e3 * fps);
|
|
1232
|
+
const endFrame = Math.max(startFrame + 1, Math.round(endMs / 1e3 * fps));
|
|
1233
|
+
for (const window of visiblePackedHostWindows(clips, startFrame, endFrame, output, effects)) {
|
|
1234
|
+
if (!window.captionVisible || window.hostClipId) continue;
|
|
1235
|
+
const code = window.candidateClipIds.length > 1 ? "behind_subject_caption_host_ambiguous" : "behind_subject_caption_host_missing";
|
|
1236
|
+
throw renderInputError(
|
|
1237
|
+
code,
|
|
1238
|
+
`Behind-subject caption ${label} is not fully covered by a unique visible packed host during frames [${window.startFrame}, ${window.endFrame}); an exact matte is required for the visible picture.`,
|
|
1239
|
+
{ candidateClipIds: window.candidateClipIds, startFrame: window.startFrame, endFrame: window.endFrame }
|
|
1240
|
+
);
|
|
1244
1241
|
}
|
|
1242
|
+
};
|
|
1243
|
+
for (const segment of captionSegments ?? []) {
|
|
1244
|
+
for (const word of segment) requireCoverage(`word '${word.text}'`, word.startMs, word.endMs);
|
|
1245
|
+
}
|
|
1246
|
+
for (const layout of captionLayouts ?? []) {
|
|
1247
|
+
const segment = captionSegments?.[layout.segmentIndex];
|
|
1248
|
+
const first = segment?.[layout.wordStartIndex];
|
|
1249
|
+
const last = segment?.[layout.holdThroughWordIndex ?? layout.wordEndIndex - 1];
|
|
1250
|
+
if (!first || !last) throw new Error("Behind-subject caption layout references missing saved transcript words");
|
|
1251
|
+
requireCoverage(
|
|
1252
|
+
`layout segment ${layout.segmentIndex} words ${layout.wordStartIndex}..${layout.wordEndIndex - 1}`,
|
|
1253
|
+
first.startMs,
|
|
1254
|
+
last.endMs
|
|
1255
|
+
);
|
|
1245
1256
|
}
|
|
1246
1257
|
}
|
|
1247
1258
|
var FULL_RECT = { left: 0, top: 0, right: 1, bottom: 1 };
|
|
@@ -1976,6 +1987,11 @@ function assertMergedCaptionFacesDrawable(composition) {
|
|
|
1976
1987
|
if (!captions?.segments.some((segment) => segment.words.length > 0)) return;
|
|
1977
1988
|
const brandCaption = composition.brandKit?.caption;
|
|
1978
1989
|
const issues = mergedCaptionFaceIssues(captions.style, brandCaption);
|
|
1990
|
+
for (const [index, layout] of (captions.layouts ?? []).entries()) {
|
|
1991
|
+
for (const issue of mergedCaptionFaceIssues({ ...captions.style, ...layout.style }, brandCaption)) {
|
|
1992
|
+
issues.push({ ...issue, path: `captions.layouts[${index}].style.${issue.path.split(".").at(-1)}` });
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1979
1995
|
if (issues.length === 0) return;
|
|
1980
1996
|
throw renderInputError(
|
|
1981
1997
|
"font_not_reproducible",
|
|
@@ -2078,9 +2094,9 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
|
|
|
2078
2094
|
if (end > maxBackgroundEndFrames) maxBackgroundEndFrames = end;
|
|
2079
2095
|
}
|
|
2080
2096
|
const bodyDurationInFrames = maxBackgroundEndFrames > 0 ? Math.min(maxEndFrames, maxBackgroundEndFrames) : maxEndFrames;
|
|
2081
|
-
const captionSegments = composition.captions?.segments.map((seg) => seg.words)
|
|
2097
|
+
const captionSegments = composition.captions?.segments.map((seg) => seg.words);
|
|
2082
2098
|
if (composition.captions?.zPlane === "behind-subject") {
|
|
2083
|
-
assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects);
|
|
2099
|
+
assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects, composition.captions.layouts);
|
|
2084
2100
|
}
|
|
2085
2101
|
const brandKit = composition.brandKit;
|
|
2086
2102
|
const { totalFrames: durationInFrames } = resolveBookendTimeline(
|
|
@@ -2102,7 +2118,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
|
|
|
2102
2118
|
...mot.exitMs != null ? { exitMs: mot.exitMs } : {}
|
|
2103
2119
|
} : void 0;
|
|
2104
2120
|
const baseStyle = composition.captions?.style;
|
|
2105
|
-
const hasCaptions = captionSegments
|
|
2121
|
+
const hasCaptions = captionSegments?.some((words) => words.length > 0) ?? false;
|
|
2106
2122
|
const captionStyle = hasCaptions && (cap || baseStyle || captionMotion) ? {
|
|
2107
2123
|
...baseStyle ?? {},
|
|
2108
2124
|
...cap?.fontFamily ? { fontFamily: cap.fontFamily } : {},
|
|
@@ -2127,6 +2143,10 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
|
|
|
2127
2143
|
...cap?.exitMs != null ? { exitMs: cap.exitMs } : {}
|
|
2128
2144
|
} : void 0;
|
|
2129
2145
|
const resolvedCaptionStyle = captionStyle ? resolveCaptionColorRefs(captionStyle, brandKit) : void 0;
|
|
2146
|
+
const captionLayouts = composition.captions?.layouts?.map((layout) => ({
|
|
2147
|
+
...layout,
|
|
2148
|
+
...layout.style ? { style: resolveCaptionColorRefs(layout.style, brandKit) } : {}
|
|
2149
|
+
}));
|
|
2130
2150
|
return {
|
|
2131
2151
|
clips,
|
|
2132
2152
|
overlays,
|
|
@@ -2138,6 +2158,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
|
|
|
2138
2158
|
...audioTracks.length > 0 ? { audioTracks } : {},
|
|
2139
2159
|
...effects.length > 0 ? { effects } : {},
|
|
2140
2160
|
...hasCaptions ? { captionSegments } : {},
|
|
2161
|
+
...hasCaptions && captionLayouts?.length ? { captionLayouts } : {},
|
|
2141
2162
|
...hasCaptions && composition.captions?.zPlane === "behind-subject" ? { captionsBehind: true } : {},
|
|
2142
2163
|
...resolvedCaptionStyle ? { captionStyle: resolvedCaptionStyle } : {},
|
|
2143
2164
|
...brandKit ? { brandKit } : {}
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
SCENE_SHOT_RESOURCE_DESCRIPTION,
|
|
3
3
|
SCENE_SHOT_RESOURCE_URI,
|
|
4
4
|
sceneShotContract
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7AUHC2X7.js";
|
|
6
6
|
import {
|
|
7
7
|
querySchemaShape,
|
|
8
8
|
serializeQuery,
|
|
9
9
|
waitForJob
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZFW35DLG.js";
|
|
11
11
|
import {
|
|
12
12
|
CURATED_TOOLS
|
|
13
13
|
} from "./chunk-4WBSXHQA.js";
|
|
@@ -28,10 +28,10 @@ import {
|
|
|
28
28
|
text,
|
|
29
29
|
textWithData,
|
|
30
30
|
withMedia
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-46DGDPXR.js";
|
|
32
32
|
import {
|
|
33
33
|
CompositionOp
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-VTM7NEJC.js";
|
|
35
35
|
|
|
36
36
|
// ../mcp/src/prompts.ts
|
|
37
37
|
var skillListCache = null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Composition
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UFNIB3JL.js";
|
|
4
4
|
import {
|
|
5
5
|
expandToLinkGroup
|
|
6
6
|
} from "./chunk-KYRDKK2D.js";
|
|
@@ -132,6 +132,56 @@ function pruneSelectionInDraft(draft, removedClipIds) {
|
|
|
132
132
|
draft.selectedOverlayId = patch.selectedOverlayId;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// ../composition/src/ops/captionOps.ts
|
|
136
|
+
function createCaptionOps(set) {
|
|
137
|
+
return {
|
|
138
|
+
setCaptionStyle: (style) => {
|
|
139
|
+
set((draft) => {
|
|
140
|
+
const clean = Object.fromEntries(
|
|
141
|
+
Object.entries(style).filter(([, v]) => v !== void 0)
|
|
142
|
+
);
|
|
143
|
+
if (Object.keys(clean).length === 0) return;
|
|
144
|
+
const existing = draft.composition.captions;
|
|
145
|
+
if (existing) {
|
|
146
|
+
existing.style = { ...existing.style ?? {}, ...clean };
|
|
147
|
+
} else {
|
|
148
|
+
draft.composition.captions = { source: "authored", segments: [], style: clean };
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
setCaptionLayouts: (layouts) => {
|
|
153
|
+
set((draft) => {
|
|
154
|
+
if (!draft.composition.captions) throw new Error("setCaptionLayouts requires saved transcript captions");
|
|
155
|
+
if (layouts.length === 0) delete draft.composition.captions.layouts;
|
|
156
|
+
else draft.composition.captions.layouts = layouts;
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
setCaptionDepth: (zPlane) => {
|
|
160
|
+
set((draft) => {
|
|
161
|
+
const existing = draft.composition.captions;
|
|
162
|
+
if (!existing) return;
|
|
163
|
+
if (zPlane === "front") {
|
|
164
|
+
delete existing.zPlane;
|
|
165
|
+
} else {
|
|
166
|
+
existing.zPlane = zPlane;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
setCaptions: (segments, style, derivedFrom) => {
|
|
171
|
+
set((draft) => {
|
|
172
|
+
const existing = draft.composition.captions;
|
|
173
|
+
const mergedStyle = style ? {
|
|
174
|
+
...existing?.style ?? {},
|
|
175
|
+
...Object.fromEntries(Object.entries(style).filter(([, v]) => v !== void 0))
|
|
176
|
+
} : existing?.style;
|
|
177
|
+
const styleField = mergedStyle && Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {};
|
|
178
|
+
const depthField = existing?.zPlane ? { zPlane: existing.zPlane } : {};
|
|
179
|
+
draft.composition.captions = derivedFrom ? { source: "derived", from: derivedFrom, segments, ...styleField, ...depthField } : { source: "authored", segments, ...styleField, ...depthField };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
135
185
|
// ../composition/src/ops/compositionOps.ts
|
|
136
186
|
function createCompositionOps(get, set) {
|
|
137
187
|
return {
|
|
@@ -447,6 +497,7 @@ function createCompositionOps(get, set) {
|
|
|
447
497
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start >= end) return;
|
|
448
498
|
const span = end - start;
|
|
449
499
|
set((draft) => {
|
|
500
|
+
if (draft.composition.captions) delete draft.composition.captions.layouts;
|
|
450
501
|
for (const track of draft.composition.tracks) {
|
|
451
502
|
const next = [];
|
|
452
503
|
for (const item of track.contents) {
|
|
@@ -581,6 +632,7 @@ function createCompositionOps(get, set) {
|
|
|
581
632
|
});
|
|
582
633
|
}
|
|
583
634
|
if (draft.composition.captions) {
|
|
635
|
+
delete draft.composition.captions.layouts;
|
|
584
636
|
for (const seg of draft.composition.captions.segments) {
|
|
585
637
|
seg.words = seg.words.flatMap((w) => {
|
|
586
638
|
const r = cutTimedRange(w.startMs, w.endMs, tStart * 1e3, tEnd * 1e3);
|
|
@@ -631,6 +683,7 @@ function createCompositionOps(get, set) {
|
|
|
631
683
|
}
|
|
632
684
|
}
|
|
633
685
|
if (draft.composition.captions) {
|
|
686
|
+
delete draft.composition.captions.layouts;
|
|
634
687
|
for (const segment of draft.composition.captions.segments) {
|
|
635
688
|
for (const word of segment.words) {
|
|
636
689
|
const shifted = shiftTimedRange(word.startMs, word.endMs, insertAt * 1e3, restored * 1e3);
|
|
@@ -667,45 +720,10 @@ function createCompositionOps(get, set) {
|
|
|
667
720
|
}
|
|
668
721
|
});
|
|
669
722
|
},
|
|
670
|
-
|
|
671
|
-
set((draft) => {
|
|
672
|
-
const clean = Object.fromEntries(
|
|
673
|
-
Object.entries(style).filter(([, v]) => v !== void 0)
|
|
674
|
-
);
|
|
675
|
-
if (Object.keys(clean).length === 0) return;
|
|
676
|
-
const existing = draft.composition.captions;
|
|
677
|
-
if (existing) {
|
|
678
|
-
existing.style = { ...existing.style ?? {}, ...clean };
|
|
679
|
-
} else {
|
|
680
|
-
draft.composition.captions = { source: "authored", segments: [], style: clean };
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
},
|
|
684
|
-
setCaptionDepth: (zPlane) => {
|
|
685
|
-
set((draft) => {
|
|
686
|
-
const existing = draft.composition.captions;
|
|
687
|
-
if (!existing) return;
|
|
688
|
-
if (zPlane === "front") {
|
|
689
|
-
delete existing.zPlane;
|
|
690
|
-
} else {
|
|
691
|
-
existing.zPlane = zPlane;
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
},
|
|
695
|
-
setCaptions: (segments, style, derivedFrom) => {
|
|
696
|
-
set((draft) => {
|
|
697
|
-
const existing = draft.composition.captions;
|
|
698
|
-
const mergedStyle = style ? {
|
|
699
|
-
...existing?.style ?? {},
|
|
700
|
-
...Object.fromEntries(Object.entries(style).filter(([, v]) => v !== void 0))
|
|
701
|
-
} : existing?.style;
|
|
702
|
-
const styleField = mergedStyle && Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {};
|
|
703
|
-
const depthField = existing?.zPlane ? { zPlane: existing.zPlane } : {};
|
|
704
|
-
draft.composition.captions = derivedFrom ? { source: "derived", from: derivedFrom, segments, ...styleField, ...depthField } : { source: "authored", segments, ...styleField, ...depthField };
|
|
705
|
-
});
|
|
706
|
-
},
|
|
723
|
+
...createCaptionOps(set),
|
|
707
724
|
ripple: (time, amount) => {
|
|
708
725
|
set((draft) => {
|
|
726
|
+
if (draft.composition.captions) delete draft.composition.captions.layouts;
|
|
709
727
|
for (const track of draft.composition.tracks) {
|
|
710
728
|
for (const item of track.contents) {
|
|
711
729
|
if (!("source" in item)) continue;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-B7J5UUUV.js";
|
|
4
4
|
import {
|
|
5
5
|
captionFaceIssues
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7RGDRDAR.js";
|
|
7
7
|
|
|
8
8
|
// ../composition/src/validators/opaqueId.ts
|
|
9
9
|
import { z } from "zod";
|
|
@@ -11,6 +11,8 @@ var OpaqueId = z.string();
|
|
|
11
11
|
|
|
12
12
|
// ../composition/src/validators/composition.ts
|
|
13
13
|
import { z as z2 } from "zod";
|
|
14
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
15
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
14
16
|
var Format = z2.object({
|
|
15
17
|
aspectRatio: z2.enum(["16:9", "9:16", "1:1", "4:5"]),
|
|
16
18
|
fps: z2.union([z2.literal(24), z2.literal(25), z2.literal(30), z2.literal(60)]).optional(),
|
|
@@ -288,6 +290,101 @@ var CaptionStyle = z2.object({
|
|
|
288
290
|
annotationColor: z2.string().optional(),
|
|
289
291
|
maxWidthPercent: z2.number().positive().max(100, "captions style.maxWidthPercent is % of composition width (max 100)").optional()
|
|
290
292
|
});
|
|
293
|
+
var CaptionLayoutStyle = CaptionStyle.pick({
|
|
294
|
+
fontFamily: true,
|
|
295
|
+
fontSize: true,
|
|
296
|
+
fontWeight: true,
|
|
297
|
+
color: true,
|
|
298
|
+
highlightColor: true,
|
|
299
|
+
backgroundColor: true,
|
|
300
|
+
plate: true,
|
|
301
|
+
textTransform: true,
|
|
302
|
+
emphasisColor: true,
|
|
303
|
+
emphasisScale: true,
|
|
304
|
+
emphasisFontFamily: true,
|
|
305
|
+
emphasisFontWeight: true,
|
|
306
|
+
emphasisTextTransform: true,
|
|
307
|
+
emphasisLetterSpacing: true,
|
|
308
|
+
emphasisFontStyle: true,
|
|
309
|
+
emphasisPlate: true,
|
|
310
|
+
emphasisPlateColor: true,
|
|
311
|
+
emphasisPlateTextColor: true,
|
|
312
|
+
emphasisPlateAltColor: true,
|
|
313
|
+
emphasisPlateAltTextColor: true,
|
|
314
|
+
emphasisPlateRadius: true,
|
|
315
|
+
emphasisPlatePadX: true,
|
|
316
|
+
emphasisPlatePadY: true,
|
|
317
|
+
annotationColor: true,
|
|
318
|
+
entrance: true,
|
|
319
|
+
entranceMs: true,
|
|
320
|
+
entranceStaggerMs: true,
|
|
321
|
+
exitMs: true
|
|
322
|
+
}).strict();
|
|
323
|
+
var CaptionLayoutInput = z2.object({
|
|
324
|
+
segmentIndex: z2.number().int().nonnegative(),
|
|
325
|
+
wordStartIndex: z2.number().int().nonnegative(),
|
|
326
|
+
wordEndIndex: z2.number().int().positive().describe("Exclusive word index in the saved caption segment."),
|
|
327
|
+
region: CaptionRegion,
|
|
328
|
+
scale: z2.number().min(0.25).max(4).optional(),
|
|
329
|
+
rotation: z2.number().min(-45).max(45).optional(),
|
|
330
|
+
style: CaptionLayoutStyle.optional(),
|
|
331
|
+
holdThroughWordIndex: z2.number().int().nonnegative().optional().describe(
|
|
332
|
+
"Inclusive saved-word index in this segment. The layout stays visible until that word ends."
|
|
333
|
+
),
|
|
334
|
+
wordMarks: z2.array(z2.object({
|
|
335
|
+
wordIndex: z2.number().int().nonnegative(),
|
|
336
|
+
emphasis: z2.boolean().optional(),
|
|
337
|
+
annotate: z2.enum(["circle", "underline"]).nullable().optional()
|
|
338
|
+
}).strict()).optional()
|
|
339
|
+
}).strict();
|
|
340
|
+
var CaptionLayout = CaptionLayoutInput.extend({
|
|
341
|
+
wordSignature: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
342
|
+
});
|
|
343
|
+
function captionSegmentSignature(words) {
|
|
344
|
+
const encoded = JSON.stringify(words.map((word) => ({
|
|
345
|
+
text: word.text,
|
|
346
|
+
startMs: word.startMs,
|
|
347
|
+
endMs: word.endMs,
|
|
348
|
+
emphasis: word.emphasis ?? null,
|
|
349
|
+
annotate: word.annotate ?? null
|
|
350
|
+
})));
|
|
351
|
+
return bytesToHex(sha256(new TextEncoder().encode(encoded)));
|
|
352
|
+
}
|
|
353
|
+
function captionLayoutIssues(segments, layouts, checkSignature = false) {
|
|
354
|
+
const issues = [];
|
|
355
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
356
|
+
layouts.forEach((layout, index) => {
|
|
357
|
+
const words = segments[layout.segmentIndex]?.words;
|
|
358
|
+
if (!words) {
|
|
359
|
+
issues.push({ index, field: "segmentIndex", message: "caption layout segment does not exist" });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (layout.wordStartIndex >= layout.wordEndIndex || layout.wordEndIndex > words.length) {
|
|
363
|
+
issues.push({ index, field: "wordEndIndex", message: "caption layout word range is outside its segment" });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const hold = layout.holdThroughWordIndex ?? layout.wordEndIndex - 1;
|
|
367
|
+
if (hold < layout.wordEndIndex - 1 || hold >= words.length) {
|
|
368
|
+
issues.push({ index, field: "holdThroughWordIndex", message: "caption layout hold must reference this or a later word in the same segment" });
|
|
369
|
+
}
|
|
370
|
+
const marked = /* @__PURE__ */ new Set();
|
|
371
|
+
for (const mark of layout.wordMarks ?? []) {
|
|
372
|
+
if (mark.wordIndex < layout.wordStartIndex || mark.wordIndex >= layout.wordEndIndex || marked.has(mark.wordIndex)) {
|
|
373
|
+
issues.push({ index, field: "wordMarks", message: "caption layout word marks must be unique words inside the layout range" });
|
|
374
|
+
}
|
|
375
|
+
marked.add(mark.wordIndex);
|
|
376
|
+
}
|
|
377
|
+
for (let word = layout.wordStartIndex; word < layout.wordEndIndex; word++) {
|
|
378
|
+
const key = `${layout.segmentIndex}:${word}`;
|
|
379
|
+
if (occupied.has(key)) issues.push({ index, field: "wordStartIndex", message: "caption layout word ranges must not overlap" });
|
|
380
|
+
occupied.add(key);
|
|
381
|
+
}
|
|
382
|
+
if (checkSignature && "wordSignature" in layout && layout.wordSignature !== captionSegmentSignature(words)) {
|
|
383
|
+
issues.push({ index, field: "wordSignature", message: "caption layout is stale for these saved transcript words; reapply setCaptionLayouts" });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return issues;
|
|
387
|
+
}
|
|
291
388
|
var CaptionSegment = z2.object({
|
|
292
389
|
words: z2.array(CaptionWord)
|
|
293
390
|
});
|
|
@@ -298,18 +395,29 @@ var CompositionCaptions = z2.discriminatedUnion("source", [
|
|
|
298
395
|
from: z2.object({ clipId: z2.string() }),
|
|
299
396
|
segments: z2.array(CaptionSegment),
|
|
300
397
|
style: CaptionStyle.optional(),
|
|
398
|
+
layouts: z2.array(CaptionLayout).optional(),
|
|
301
399
|
zPlane: CaptionDepth
|
|
302
400
|
}),
|
|
303
401
|
z2.object({
|
|
304
402
|
source: z2.literal("authored"),
|
|
305
403
|
segments: z2.array(CaptionSegment),
|
|
306
404
|
style: CaptionStyle.optional(),
|
|
405
|
+
layouts: z2.array(CaptionLayout).optional(),
|
|
307
406
|
zPlane: CaptionDepth
|
|
308
407
|
})
|
|
309
408
|
]).superRefine((captions, ctx) => {
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
409
|
+
if (captions.style) {
|
|
410
|
+
for (const issue of captionFaceIssues(captions.style)) {
|
|
411
|
+
ctx.addIssue({ code: "custom", path: ["style", issue.field], message: issue.message });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
for (const issue of captionLayoutIssues(captions.segments, captions.layouts ?? [], true)) {
|
|
415
|
+
ctx.addIssue({ code: "custom", path: ["layouts", issue.index, issue.field], message: issue.message });
|
|
416
|
+
}
|
|
417
|
+
for (const [index, layout] of (captions.layouts ?? []).entries()) {
|
|
418
|
+
for (const issue of captionFaceIssues({ ...captions.style, ...layout.style })) {
|
|
419
|
+
ctx.addIssue({ code: "custom", path: ["layouts", index, "style", issue.field], message: issue.message });
|
|
420
|
+
}
|
|
313
421
|
}
|
|
314
422
|
});
|
|
315
423
|
function captionsAreBehindSubject(captions) {
|
|
@@ -540,6 +648,11 @@ export {
|
|
|
540
648
|
Marker,
|
|
541
649
|
CaptionWord,
|
|
542
650
|
CaptionStyle,
|
|
651
|
+
CaptionLayoutStyle,
|
|
652
|
+
CaptionLayoutInput,
|
|
653
|
+
CaptionLayout,
|
|
654
|
+
captionSegmentSignature,
|
|
655
|
+
captionLayoutIssues,
|
|
543
656
|
CaptionSegment,
|
|
544
657
|
CompositionCaptions,
|
|
545
658
|
captionsAreBehindSubject,
|