@cueframe/kernel 0.2.51 → 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.
Files changed (74) hide show
  1. package/dist/_members/composition/fonts/bundledFaces.d.ts +9 -15
  2. package/dist/_members/composition/fonts/captionFaces.d.ts +2 -3
  3. package/dist/_members/composition/ops/captionOps.d.ts +11 -0
  4. package/dist/_members/composition/ops/compositionOps.d.ts +20 -19
  5. package/dist/_members/composition/validators/composition.d.ts +664 -0
  6. package/dist/_members/composition/validators/registry.d.ts +1 -1
  7. package/dist/_members/composition/validators/render.d.ts +294 -0
  8. package/dist/_members/composition-ops/index.d.ts +86 -0
  9. package/dist/_members/core/render/compositionAudioAndDepth.d.ts +9 -3
  10. package/dist/_members/core/render/compositionRender.d.ts +14 -0
  11. package/dist/_members/core/render/index.d.ts +2 -2
  12. package/dist/_members/core/render/types.d.ts +4 -1
  13. package/dist/_members/plane/contract.d.ts +1 -1
  14. package/dist/_members/primitives/captions/Captions.d.ts +18 -1
  15. package/dist/_members/primitives/captions/index.d.ts +1 -1
  16. package/dist/animate.js +2 -2
  17. package/dist/authoring/build-tools.js +6 -6
  18. package/dist/authoring/context.js +5 -5
  19. package/dist/authoring/tools.js +5 -5
  20. package/dist/authoring.js +9 -9
  21. package/dist/{chunk-MYBK6DKF.js → chunk-2BVKNYRB.js} +1 -1
  22. package/dist/{chunk-2LUAUF46.js → chunk-46DGDPXR.js} +303 -47
  23. package/dist/{chunk-KY3QBG7O.js → chunk-4DM63RTI.js} +4 -4
  24. package/dist/{chunk-IHWC75AN.js → chunk-5E4YP44R.js} +1 -1
  25. package/dist/{chunk-MOUWO53S.js → chunk-7AUHC2X7.js} +1 -1
  26. package/dist/{chunk-AVTEPAWI.js → chunk-7RGDRDAR.js} +17 -13
  27. package/dist/{chunk-7XZW26U2.js → chunk-AONWKKXK.js} +1 -1
  28. package/dist/{chunk-O3SJTX6S.js → chunk-C6ONUJIO.js} +5 -5
  29. package/dist/{chunk-LO3DH776.js → chunk-CJMUTCJW.js} +2 -2
  30. package/dist/{chunk-CZOVEX2D.js → chunk-CR3G2JQP.js} +1 -1
  31. package/dist/{chunk-2PU2TSUG.js → chunk-D5E5XFHK.js} +1 -1
  32. package/dist/{chunk-6WQHVYJD.js → chunk-G7ZSFSQG.js} +1 -1
  33. package/dist/{chunk-7SXY6NFW.js → chunk-GAACUQAB.js} +4 -4
  34. package/dist/{chunk-4CNJGY7P.js → chunk-LPUQTBKU.js} +147 -27
  35. package/dist/{chunk-YKXOAGY3.js → chunk-P3DYA5NA.js} +8 -8
  36. package/dist/{chunk-Z6NY6MCL.js → chunk-RMLGMYEJ.js} +1 -1
  37. package/dist/{chunk-NLXQ7WTH.js → chunk-TPKPU4PE.js} +56 -38
  38. package/dist/{chunk-LZEX2GAY.js → chunk-UFNIB3JL.js} +144 -5
  39. package/dist/{chunk-ACURZCPN.js → chunk-UKKY7HWN.js} +1 -1
  40. package/dist/{chunk-M665ZQYB.js → chunk-VTM7NEJC.js} +38 -6
  41. package/dist/{chunk-FSA3UBVR.js → chunk-ZFW35DLG.js} +1 -1
  42. package/dist/{chunk-OVV35ZS2.js → chunk-ZIPGDGBR.js} +1 -1
  43. package/dist/component-runtime/render.js +2 -2
  44. package/dist/component-runtime.js +3 -3
  45. package/dist/compose.js +23 -23
  46. package/dist/composition/fonts.js +1 -1
  47. package/dist/composition/graphics-plan.js +3 -3
  48. package/dist/composition/ops.js +4 -4
  49. package/dist/composition/projection.js +3 -3
  50. package/dist/composition/validators.js +17 -3
  51. package/dist/composition-ops.js +8 -8
  52. package/dist/composition.js +19 -5
  53. package/dist/{context-AIUJG7R4.js → context-UAS5WJRB.js} +5 -5
  54. package/dist/core/render.js +6 -4
  55. package/dist/core.js +4 -4
  56. package/dist/mcp/adapter.js +2 -2
  57. package/dist/mcp/core.js +1 -1
  58. package/dist/mcp/scene-shot.js +2 -2
  59. package/dist/mcp/server.js +12 -12
  60. package/dist/mcp.js +12 -12
  61. package/dist/primitives/captions.js +195 -32
  62. package/dist/primitives/effects.js +4 -4
  63. package/dist/primitives/hero-text.js +2 -2
  64. package/dist/primitives/layouts.js +3 -3
  65. package/dist/primitives/scenes.js +4 -4
  66. package/dist/primitives/text.js +2 -2
  67. package/dist/primitives/ui-blocks.js +4 -4
  68. package/dist/primitives.js +23 -23
  69. package/dist/render-harness/cardFonts.js +2 -2
  70. package/dist/render-harness.js +12 -12
  71. package/package.json +12 -12
  72. package/dist/{chunk-RL5SO653.js → chunk-7SQOXIWR.js} +3 -3
  73. package/dist/{chunk-B3GSTQLR.js → chunk-VLJOIUHL.js} +6 -6
  74. package/dist/{chunk-RBWS5XHE.js → chunk-X34KW2SL.js} +4 -4
@@ -2,7 +2,7 @@ import {
2
2
  COMPOSE_PLANNER_TOOLS,
3
3
  COMPOSE_ROLE_TOOLS,
4
4
  dispatchComposeRoleTool
5
- } from "./chunk-KY3QBG7O.js";
5
+ } from "./chunk-4DM63RTI.js";
6
6
 
7
7
  // ../authoring/src/agent/buildTools.ts
8
8
  import { tool } from "ai";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bodySchemaFor,
3
3
  opById
4
- } from "./chunk-2LUAUF46.js";
4
+ } from "./chunk-46DGDPXR.js";
5
5
 
6
6
  // ../mcp/src/generated/sceneContract.ts
7
7
  function deepFreeze(value) {
@@ -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
- if (bundledFontFamilyFaces(family)) {
117
- return style === BUNDLED_FONT_STYLE ? "fontWeight" : "fontStyle";
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, and it ships no italic binary at all, so 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.`
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: use.field,
279
+ field: weightField,
279
280
  face,
280
- message: `font_not_reproducible \u2014 ${because(use)}: ${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 ${use.field} to a weight the family covers, or pick a family whose files do.`
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
- return captionStackFamily(stack).kind === "unknown" ? ["normal", "italic"] : ["normal"];
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
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-CL5Y6V6K.js";
32
32
  import {
33
33
  BUNDLED_FONT_FACES
34
- } from "./chunk-AVTEPAWI.js";
34
+ } from "./chunk-7RGDRDAR.js";
35
35
 
36
36
  // ../execution/component-runtime/src/sceneAdmission.ts
37
37
  import { ZodError } from "zod";
@@ -1,7 +1,3 @@
1
- import {
2
- frostedGlassWipe,
3
- spatialPush
4
- } from "./chunk-QGAWDLYX.js";
5
1
  import {
6
2
  DashboardPopulate
7
3
  } from "./chunk-S5H6GH6M.js";
@@ -11,6 +7,10 @@ import {
11
7
  import {
12
8
  EASING_BY_INTENT
13
9
  } from "./chunk-MFHXCHRI.js";
10
+ import {
11
+ frostedGlassWipe,
12
+ spatialPush
13
+ } from "./chunk-QGAWDLYX.js";
14
14
  import {
15
15
  ACCENT,
16
16
  ACCENT_SOFT,
@@ -43,7 +43,7 @@ import {
43
43
  makeDefaultScreenTexture,
44
44
  resolveCamera,
45
45
  useImageTexture
46
- } from "./chunk-2PU2TSUG.js";
46
+ } from "./chunk-D5E5XFHK.js";
47
47
  import {
48
48
  DEVICE_GLASS_OPACITY_ENCODED,
49
49
  DeviceMesh,
@@ -4,10 +4,10 @@ import {
4
4
  Format,
5
5
  GraphicRole,
6
6
  OpaqueId
7
- } from "./chunk-LZEX2GAY.js";
7
+ } from "./chunk-UFNIB3JL.js";
8
8
  import {
9
9
  brandKitCaptionFaceIssues
10
- } from "./chunk-AVTEPAWI.js";
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-2PU2TSUG.js";
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";
@@ -34,7 +34,7 @@ import {
34
34
  } from "./chunk-CL5Y6V6K.js";
35
35
  import {
36
36
  resolveCaptionFaces
37
- } from "./chunk-AVTEPAWI.js";
37
+ } from "./chunk-7RGDRDAR.js";
38
38
 
39
39
  // ../execution/animate/src/easing/smooth.ts
40
40
  var sigmoid = (x) => 1 / (1 + Math.exp(-x));
@@ -2,7 +2,7 @@ import {
2
2
  activeFaceKeepOutSeries,
3
3
  boundingBox,
4
4
  resolveBrandRefsDeep
5
- } from "./chunk-Z6NY6MCL.js";
5
+ } from "./chunk-RMLGMYEJ.js";
6
6
  import {
7
7
  CueframeError,
8
8
  encodeError
@@ -1,9 +1,9 @@
1
- import {
2
- FitText
3
- } from "./chunk-XHGZ2E77.js";
4
1
  import {
5
2
  EASING_BY_INTENT
6
3
  } from "./chunk-MFHXCHRI.js";
4
+ import {
5
+ FitText
6
+ } from "./chunk-XHGZ2E77.js";
7
7
  import {
8
8
  ACCENT,
9
9
  ACCENT_STRONG,
@@ -26,7 +26,7 @@ import {
26
26
  perspectiveFor,
27
27
  resolveCamera,
28
28
  useStage
29
- } from "./chunk-2PU2TSUG.js";
29
+ } from "./chunk-D5E5XFHK.js";
30
30
 
31
31
  // ../execution/primitives/src/effects/blur/index.tsx
32
32
  import React2 from "react";
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-VZ7TYX6Z.js";
32
32
  import {
33
33
  resolveBrandRef
34
- } from "./chunk-Z6NY6MCL.js";
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-AVTEPAWI.js";
67
+ } from "./chunk-7RGDRDAR.js";
68
68
 
69
69
  // ../execution/core/src/render/sceneCaptureIdentity.ts
70
70
  var INTENT_FIELDS = [
@@ -1226,29 +1226,41 @@ function bindBehindSubjectHosts(overlays, clips) {
1226
1226
  );
1227
1227
  }
1228
1228
  }
1229
- function assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, output, effects = []) {
1230
- for (const segment of captionSegments ?? []) {
1231
- for (const word of segment) {
1232
- const startFrame = Math.round(word.startMs / 1e3 * fps);
1233
- const endFrame = Math.max(startFrame + 1, Math.round(word.endMs / 1e3 * fps));
1234
- for (const window of visiblePackedHostWindows(clips, startFrame, endFrame, output, effects)) {
1235
- if (window.hostClipId) continue;
1236
- const code = window.candidateClipIds.length > 1 ? "behind_subject_caption_host_ambiguous" : "behind_subject_caption_host_missing";
1237
- throw renderInputError(
1238
- code,
1239
- `Behind-subject caption word '${word.text}' 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.`,
1240
- { candidateClipIds: window.candidateClipIds, startFrame: window.startFrame, endFrame: window.endFrame }
1241
- );
1242
- }
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
+ );
1243
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
+ );
1244
1256
  }
1245
1257
  }
1246
1258
  var FULL_RECT = { left: 0, top: 0, right: 1, bottom: 1 };
1247
1259
  function hasClipEffect(clip, effects) {
1248
- return effects.some((effect) => effect.target.kind === "clip" && effect.trackId === clip.trackId && effect.contentIndex === clip.contentIndex);
1260
+ return clip.hasClipEffects === true || effects.some((effect) => effect.target.kind === "clip" && effect.trackId === clip.trackId && effect.contentIndex === clip.contentIndex);
1249
1261
  }
1250
1262
  function pictureRect(clip, output, effects) {
1251
- if (clip.cropTrajectory || clip.cropTrajectoryUrl || hasClipEffect(clip, effects)) return void 0;
1263
+ if (clip.cropTrajectory || clip.cropTrajectoryUrl || clip.hasCropIntent || hasClipEffect(clip, effects)) return void 0;
1252
1264
  if (clip.fit !== "contain") return FULL_RECT;
1253
1265
  const sourceWidth = clip.sourceWidth;
1254
1266
  const sourceHeight = clip.sourceHeight;
@@ -1271,7 +1283,7 @@ function coversLowerPictures(upper, lower, output, effects) {
1271
1283
  return upperRect.left <= rect.left + epsilon && upperRect.top <= rect.top + epsilon && upperRect.right + epsilon >= rect.right && upperRect.bottom + epsilon >= rect.bottom;
1272
1284
  });
1273
1285
  }
1274
- function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects = []) {
1286
+ function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects = [], hostMode = "packed-only") {
1275
1287
  if (endFrame <= startFrame) return [];
1276
1288
  const boundaries = /* @__PURE__ */ new Set([startFrame, endFrame]);
1277
1289
  for (const clip of clips) {
@@ -1280,6 +1292,13 @@ function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects =
1280
1292
  if (start > startFrame && start < endFrame) boundaries.add(start);
1281
1293
  if (end > startFrame && end < endFrame) boundaries.add(end);
1282
1294
  }
1295
+ for (const effect of effects) {
1296
+ if (effect.target.kind !== "global") continue;
1297
+ const start = effect.startFrame ?? startFrame;
1298
+ const end = start + (effect.durationInFrames ?? endFrame - start);
1299
+ if (start > startFrame && start < endFrame) boundaries.add(start);
1300
+ if (end > startFrame && end < endFrame) boundaries.add(end);
1301
+ }
1283
1302
  const points = [...boundaries].sort((a, b) => a - b);
1284
1303
  const windows = [];
1285
1304
  for (let i = 0; i < points.length - 1; i++) {
@@ -1290,6 +1309,7 @@ function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects =
1290
1309
  );
1291
1310
  const topLayer = Math.max(...active.map((clip) => clip.layerIndex));
1292
1311
  const top = active.filter((clip) => clip.layerIndex === topLayer);
1312
+ const globalEffectActive = effects.some((effect) => effect.target.kind === "global" && (effect.startFrame ?? startFrame) <= start && (effect.startFrame ?? startFrame) + (effect.durationInFrames ?? endFrame - startFrame) >= end);
1293
1313
  const fullFrame = top[0]?.region === void 0 || top[0].region.x === 0 && top[0].region.y === 0 && top[0].region.w === 1 && top[0].region.h === 1;
1294
1314
  const provesOcclusion = top.length === 1 && fullFrame && (active.length === 1 || coversLowerPictures(
1295
1315
  top[0],
@@ -1297,18 +1317,22 @@ function visiblePackedHostWindows(clips, startFrame, endFrame, output, effects =
1297
1317
  output,
1298
1318
  effects
1299
1319
  ));
1300
- const host = provesOcclusion && top[0].packedUrl ? top[0] : void 0;
1301
- const unresolvedOverlap = top.length > 1 || top.length === 1 && !!top[0].packedUrl && active.length > 1 && !provesOcclusion;
1302
- const candidates = unresolvedOverlap ? active.filter((clip) => clip.packedUrl).map((clip) => clip.id) : top.map((clip) => clip.id);
1320
+ const suppressesCaption = provesOcclusion && !globalEffectActive && pictureRect(top[0], output, effects) !== void 0;
1321
+ const captionVisible = !(suppressesCaption && top[0].captionVisibility === "hidden");
1322
+ const candidateHost = (clip) => hostMode === "all-video-candidates" ? clip.mediaType !== "image" : !!clip.packedUrl;
1323
+ const host = provesOcclusion && candidateHost(top[0]) ? top[0] : void 0;
1324
+ const unresolvedOverlap = top.length > 1 || top.length === 1 && active.length > 1 && !provesOcclusion;
1325
+ const candidates = unresolvedOverlap ? active.filter(candidateHost).map((clip) => clip.id) : top.map((clip) => clip.id);
1303
1326
  const previous = windows[windows.length - 1];
1304
- if (previous && previous.endFrame === start && previous.hostClipId === host?.id && previous.candidateClipIds.join("\0") === candidates.join("\0")) {
1327
+ if (previous && previous.endFrame === start && previous.hostClipId === host?.id && previous.captionVisible === captionVisible && previous.candidateClipIds.join("\0") === candidates.join("\0")) {
1305
1328
  previous.endFrame = end;
1306
1329
  } else {
1307
1330
  windows.push({
1308
1331
  startFrame: start,
1309
1332
  endFrame: end,
1310
1333
  ...host ? { hostClipId: host.id } : {},
1311
- candidateClipIds: candidates
1334
+ candidateClipIds: candidates,
1335
+ captionVisible
1312
1336
  });
1313
1337
  }
1314
1338
  }
@@ -1455,6 +1479,13 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1455
1479
  );
1456
1480
  }
1457
1481
  if (rendersNoFrames(c)) continue;
1482
+ if (c.captionVisibility !== void 0 && track.kind !== "video") {
1483
+ throw renderInputError(
1484
+ "invalid_composition",
1485
+ `Clip ${c.id}: captionVisibility is supported only on video media clips.`,
1486
+ { trackId: track.id, clipId: c.id, compositionPath: `composition.tracks[${track.id}].contents[${i}].captionVisibility` }
1487
+ );
1488
+ }
1458
1489
  const src = resolveSrcUrl(c.source.mediaId, srcUrls, {
1459
1490
  trackId: track.id,
1460
1491
  clipId: c.id,
@@ -1546,6 +1577,7 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1546
1577
  ...c.animate !== void 0 ? { animate: c.animate } : {},
1547
1578
  ...c.region !== void 0 ? { region: c.region } : {},
1548
1579
  ...c.fit !== void 0 ? { fit: c.fit } : {},
1580
+ ...c.captionVisibility !== void 0 ? { captionVisibility: c.captionVisibility } : {},
1549
1581
  ...s === 0 && c.transitionIn ? {
1550
1582
  transitionIn: mapTransitionType(c.transitionIn.type),
1551
1583
  transitionDurationFrames: Math.max(1, Math.round(c.transitionIn.duration * fps))
@@ -1813,6 +1845,71 @@ function projectEffectTrack(track, trackIndex, trackCount, fps) {
1813
1845
  }
1814
1846
 
1815
1847
  // ../execution/core/src/render/compositionRender.ts
1848
+ function captionVisibilityWindowsForComposition(composition, sourceDimensions) {
1849
+ const { fps, width, height } = compositionFormatDimensions(composition.format);
1850
+ const clips = [];
1851
+ const mediaIdByProjectedClipId = {};
1852
+ let endFrame = 0;
1853
+ const trackCount = composition.tracks.length;
1854
+ const effects = composition.tracks.flatMap(
1855
+ (track, index) => track.enabled !== false && track.kind === "effect" ? projectEffectTrack(track, index, trackCount, fps) : []
1856
+ );
1857
+ for (const [trackIndex, track] of composition.tracks.entries()) {
1858
+ if (track.enabled === false || track.kind !== "video" && track.kind !== "image") continue;
1859
+ for (const [contentIndex, item] of track.contents.entries()) {
1860
+ if (!("source" in item)) continue;
1861
+ if (item.source.kind !== "media") {
1862
+ throw renderInputError(
1863
+ "clip_source_track_mismatch",
1864
+ `Clip ${item.id} on video track ${track.id} must use a media source.`,
1865
+ { trackId: track.id, clipId: item.id }
1866
+ );
1867
+ }
1868
+ const spans = projectMediaPlaybackSpans(track.kind === "video" ? {
1869
+ startTime: item.startTime,
1870
+ duration: item.duration,
1871
+ trim: item.source.trim,
1872
+ excludedRanges: item.source.excludedRanges,
1873
+ playbackRate: item.source.playbackRate,
1874
+ fps
1875
+ } : { startTime: item.startTime, duration: item.duration, fps });
1876
+ const dimensions = sourceDimensions[item.source.mediaId];
1877
+ const validDimensions = track.kind === "video" && dimensions && Number.isFinite(dimensions.width) && dimensions.width > 0 && Number.isFinite(dimensions.height) && dimensions.height > 0;
1878
+ for (const [spanIndex, span] of spans.entries()) {
1879
+ if (span.durationInFrames === 0) continue;
1880
+ const id = spans.length === 1 ? item.id : `${item.id}#${spanIndex}`;
1881
+ clips.push({
1882
+ id,
1883
+ trackId: track.id,
1884
+ layerIndex: trackCount - trackIndex,
1885
+ contentIndex,
1886
+ timelineStartFrames: span.timelineStartFrames,
1887
+ durationInFrames: span.durationInFrames,
1888
+ ...track.kind === "image" ? { mediaType: "image" } : {},
1889
+ ...validDimensions ? { sourceWidth: dimensions.width, sourceHeight: dimensions.height } : {},
1890
+ ...item.region ? { region: item.region } : {},
1891
+ ...item.fit ? { fit: item.fit } : {},
1892
+ ...track.kind === "video" && item.captionVisibility ? { captionVisibility: item.captionVisibility } : {},
1893
+ ...item.source.reframe?.segments.length ? { hasCropIntent: true } : {},
1894
+ ...item.effects?.length ? { hasClipEffects: true } : {}
1895
+ });
1896
+ if (track.kind === "video") mediaIdByProjectedClipId[id] = item.source.mediaId;
1897
+ endFrame = Math.max(endFrame, span.timelineStartFrames + span.durationInFrames);
1898
+ }
1899
+ }
1900
+ }
1901
+ for (const segment of composition.captions?.segments ?? []) {
1902
+ for (const word of segment.words) {
1903
+ const startFrame = Math.round(word.startMs / 1e3 * fps);
1904
+ endFrame = Math.max(endFrame, Math.max(startFrame + 1, Math.round(word.endMs / 1e3 * fps)));
1905
+ }
1906
+ }
1907
+ return {
1908
+ fps,
1909
+ windows: visiblePackedHostWindows(clips, 0, endFrame, { width, height }, effects, "all-video-candidates"),
1910
+ mediaIdByProjectedClipId
1911
+ };
1912
+ }
1816
1913
  var CAPTION_COLOR_FIELDS = [
1817
1914
  "color",
1818
1915
  "highlightColor",
@@ -1890,6 +1987,11 @@ function assertMergedCaptionFacesDrawable(composition) {
1890
1987
  if (!captions?.segments.some((segment) => segment.words.length > 0)) return;
1891
1988
  const brandCaption = composition.brandKit?.caption;
1892
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
+ }
1893
1995
  if (issues.length === 0) return;
1894
1996
  throw renderInputError(
1895
1997
  "font_not_reproducible",
@@ -1903,6 +2005,18 @@ function assertMergedCaptionFacesDrawable(composition) {
1903
2005
  function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUrls, alphaUrls, subCompositions, trajectories, options) {
1904
2006
  const flattened = hasCompoundClips(composition) ? flattenCompoundClips(composition, (id) => subCompositions?.[id]) : composition;
1905
2007
  composition = flattened;
2008
+ for (const track of composition.tracks) {
2009
+ for (const content of track.contents) {
2010
+ if (!("source" in content) || content.captionVisibility === void 0) continue;
2011
+ if (track.kind !== "video" || content.source.kind !== "media") {
2012
+ throw renderInputError(
2013
+ "invalid_composition",
2014
+ `Clip ${content.id}: captionVisibility is supported only on video media clips.`,
2015
+ { trackId: track.id, clipId: content.id, compositionPath: `composition.tracks[${track.id}].contents[${content.id}].captionVisibility` }
2016
+ );
2017
+ }
2018
+ }
2019
+ }
1906
2020
  assertMergedCaptionFacesDrawable(composition);
1907
2021
  const { width, height, fps } = compositionFormatDimensions(composition.format);
1908
2022
  const clips = [];
@@ -1980,9 +2094,9 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
1980
2094
  if (end > maxBackgroundEndFrames) maxBackgroundEndFrames = end;
1981
2095
  }
1982
2096
  const bodyDurationInFrames = maxBackgroundEndFrames > 0 ? Math.min(maxEndFrames, maxBackgroundEndFrames) : maxEndFrames;
1983
- const captionSegments = composition.captions?.segments.map((seg) => seg.words).filter((words) => words.length > 0);
2097
+ const captionSegments = composition.captions?.segments.map((seg) => seg.words);
1984
2098
  if (composition.captions?.zPlane === "behind-subject") {
1985
- assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects);
2099
+ assertBehindSubjectCaptionCoverage(captionSegments, clips, fps, { width, height }, effects, composition.captions.layouts);
1986
2100
  }
1987
2101
  const brandKit = composition.brandKit;
1988
2102
  const { totalFrames: durationInFrames } = resolveBookendTimeline(
@@ -2004,7 +2118,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2004
2118
  ...mot.exitMs != null ? { exitMs: mot.exitMs } : {}
2005
2119
  } : void 0;
2006
2120
  const baseStyle = composition.captions?.style;
2007
- const hasCaptions = captionSegments != null && captionSegments.length > 0;
2121
+ const hasCaptions = captionSegments?.some((words) => words.length > 0) ?? false;
2008
2122
  const captionStyle = hasCaptions && (cap || baseStyle || captionMotion) ? {
2009
2123
  ...baseStyle ?? {},
2010
2124
  ...cap?.fontFamily ? { fontFamily: cap.fontFamily } : {},
@@ -2029,6 +2143,10 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2029
2143
  ...cap?.exitMs != null ? { exitMs: cap.exitMs } : {}
2030
2144
  } : void 0;
2031
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
+ }));
2032
2150
  return {
2033
2151
  clips,
2034
2152
  overlays,
@@ -2040,6 +2158,7 @@ function compositionToRenderInput(composition, srcUrls, trajectoryUrls, coViewUr
2040
2158
  ...audioTracks.length > 0 ? { audioTracks } : {},
2041
2159
  ...effects.length > 0 ? { effects } : {},
2042
2160
  ...hasCaptions ? { captionSegments } : {},
2161
+ ...hasCaptions && captionLayouts?.length ? { captionLayouts } : {},
2043
2162
  ...hasCaptions && composition.captions?.zPlane === "behind-subject" ? { captionsBehind: true } : {},
2044
2163
  ...resolvedCaptionStyle ? { captionStyle: resolvedCaptionStyle } : {},
2045
2164
  ...brandKit ? { brandKit } : {}
@@ -3086,6 +3205,7 @@ export {
3086
3205
  visiblePackedHostWindows,
3087
3206
  playableSpans,
3088
3207
  projectMediaPlaybackSpans,
3208
+ captionVisibilityWindowsForComposition,
3089
3209
  compositionFormatDimensions,
3090
3210
  referencedMediaIds,
3091
3211
  compositionToRenderInput,
@@ -1,16 +1,16 @@
1
+ import {
2
+ SCENE_SHOT_RESOURCE_DESCRIPTION,
3
+ SCENE_SHOT_RESOURCE_URI,
4
+ sceneShotContract
5
+ } from "./chunk-7AUHC2X7.js";
1
6
  import {
2
7
  querySchemaShape,
3
8
  serializeQuery,
4
9
  waitForJob
5
- } from "./chunk-FSA3UBVR.js";
10
+ } from "./chunk-ZFW35DLG.js";
6
11
  import {
7
12
  CURATED_TOOLS
8
13
  } from "./chunk-4WBSXHQA.js";
9
- import {
10
- SCENE_SHOT_RESOURCE_DESCRIPTION,
11
- SCENE_SHOT_RESOURCE_URI,
12
- sceneShotContract
13
- } from "./chunk-MOUWO53S.js";
14
14
  import {
15
15
  GENERATED_OPERATIONS,
16
16
  apiErrorFrom,
@@ -28,10 +28,10 @@ import {
28
28
  text,
29
29
  textWithData,
30
30
  withMedia
31
- } from "./chunk-2LUAUF46.js";
31
+ } from "./chunk-46DGDPXR.js";
32
32
  import {
33
33
  CompositionOp
34
- } from "./chunk-M665ZQYB.js";
34
+ } from "./chunk-VTM7NEJC.js";
35
35
 
36
36
  // ../mcp/src/prompts.ts
37
37
  var skillListCache = null;
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  captionFontSource,
12
12
  effectiveCaptionFamily
13
- } from "./chunk-AVTEPAWI.js";
13
+ } from "./chunk-7RGDRDAR.js";
14
14
 
15
15
  // ../composition/src/projection/brandKit.ts
16
16
  var LOGO_DEFAULT_PLACEMENT = "br";