@cueframe/kernel 0.2.45 → 0.2.47

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 (61) hide show
  1. package/dist/_members/composition/ops/compositionOps.shared.d.ts +1 -1
  2. package/dist/_members/composition/projection/cropProjection.d.ts +2 -1
  3. package/dist/_members/composition-ops/index.d.ts +4 -0
  4. package/dist/_members/composition-ops/materializeCaptions.d.ts +19 -0
  5. package/dist/_members/core/render/compositionRender.d.ts +1 -1
  6. package/dist/_members/core/render/index.d.ts +1 -1
  7. package/dist/_members/core/render/trackProjection.d.ts +24 -0
  8. package/dist/_members/core/render/types.d.ts +1 -0
  9. package/dist/_members/errors/registry.d.ts +18 -0
  10. package/dist/_members/mcp/generated/harness.d.ts +1 -1
  11. package/dist/_members/mcp/server.d.ts +2 -0
  12. package/dist/authoring/build-tools.js +3 -3
  13. package/dist/authoring/context.js +2 -2
  14. package/dist/authoring/tools.js +2 -2
  15. package/dist/authoring.js +6 -6
  16. package/dist/{chunk-S3HHEJMZ.js → chunk-4FKNPTLX.js} +1 -1
  17. package/dist/{chunk-JVWLZNHC.js → chunk-ACURZCPN.js} +1 -1
  18. package/dist/{chunk-46PFSJZE.js → chunk-AOIY7NGF.js} +3059 -3120
  19. package/dist/{chunk-MJWRC7G7.js → chunk-BBNF4QLD.js} +2 -2
  20. package/dist/{chunk-5YO4LAB7.js → chunk-CFIQY4OE.js} +1 -1
  21. package/dist/{chunk-VVKBCBNR.js → chunk-DV7ZFVL4.js} +1 -1
  22. package/dist/{chunk-ULQJWGUV.js → chunk-E63UEO5C.js} +3 -0
  23. package/dist/{chunk-AMFFWX3C.js → chunk-FEG264CF.js} +55 -32
  24. package/dist/{chunk-EDBQPQAG.js → chunk-FGJ7GKWZ.js} +6 -6
  25. package/dist/{chunk-SPF3AXTB.js → chunk-FYS2XUKJ.js} +1 -1
  26. package/dist/{chunk-QISFYLBB.js → chunk-IMIUHG2E.js} +1 -1
  27. package/dist/{chunk-CYILSHK5.js → chunk-JPMVNMF3.js} +1 -1
  28. package/dist/chunk-KCXADPOC.js +1085 -0
  29. package/dist/{chunk-4S2YZQJP.js → chunk-N54EGUVM.js} +1 -1
  30. package/dist/{chunk-HUZN2HRT.js → chunk-OYWX4NEP.js} +48 -9
  31. package/dist/{chunk-ZUJCWFS7.js → chunk-SLUTOUWI.js} +1 -1
  32. package/dist/{chunk-DENF4TPI.js → chunk-T56BK7NS.js} +6 -4
  33. package/dist/{chunk-B4VMU2IJ.js → chunk-TBHSOEYK.js} +1 -1
  34. package/dist/{chunk-POTCIQTA.js → chunk-VEQTURKN.js} +90 -11
  35. package/dist/{chunk-OZNULDQE.js → chunk-XSBVOXSB.js} +1 -1
  36. package/dist/{chunk-URZR6C3N.js → chunk-Z4G3VM3H.js} +1 -1
  37. package/dist/compose.js +1 -1
  38. package/dist/composition/invariants.js +2 -2
  39. package/dist/composition/ops.js +1 -1
  40. package/dist/composition/projection.js +2 -2
  41. package/dist/composition/timeline.js +2 -2
  42. package/dist/composition-ops.js +36 -894
  43. package/dist/composition.js +4 -4
  44. package/dist/{context-KNFL62QR.js → context-YNR73FNI.js} +2 -2
  45. package/dist/core/render.js +7 -5
  46. package/dist/core/select.js +2 -2
  47. package/dist/core.js +5 -5
  48. package/dist/director/ai-sdk.js +3 -3
  49. package/dist/director/domain.js +2 -2
  50. package/dist/director.js +3 -3
  51. package/dist/errors.js +1 -1
  52. package/dist/mcp/adapter.js +3 -3
  53. package/dist/mcp/core.js +1 -1
  54. package/dist/mcp/curation.js +1 -1
  55. package/dist/mcp/scene-shot.js +2 -2
  56. package/dist/mcp/server.js +26 -5
  57. package/dist/mcp.js +28 -9
  58. package/dist/render-harness/cardAssetAdmission.js +2 -2
  59. package/dist/render-harness/cardFonts.js +2 -2
  60. package/dist/render-harness.js +7 -7
  61. package/package.json +11 -11
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  decodeError,
11
11
  scrubSensitive
12
- } from "./chunk-ULQJWGUV.js";
12
+ } from "./chunk-E63UEO5C.js";
13
13
 
14
14
  // ../director/src/domain/chat.ts
15
15
  import { z } from "zod";
@@ -1,16 +1,16 @@
1
+ import {
2
+ SCENE_SHOT_RESOURCE_DESCRIPTION,
3
+ SCENE_SHOT_RESOURCE_URI,
4
+ sceneShotContract
5
+ } from "./chunk-FYS2XUKJ.js";
1
6
  import {
2
7
  querySchemaShape,
3
8
  serializeQuery,
4
9
  waitForJob
5
- } from "./chunk-OZNULDQE.js";
10
+ } from "./chunk-XSBVOXSB.js";
6
11
  import {
7
12
  CURATED_TOOLS
8
- } from "./chunk-5YO4LAB7.js";
9
- import {
10
- SCENE_SHOT_RESOURCE_DESCRIPTION,
11
- SCENE_SHOT_RESOURCE_URI,
12
- sceneShotContract
13
- } from "./chunk-SPF3AXTB.js";
13
+ } from "./chunk-CFIQY4OE.js";
14
14
  import {
15
15
  GENERATED_OPERATIONS,
16
16
  apiErrorFrom,
@@ -28,7 +28,10 @@ import {
28
28
  text,
29
29
  textWithData,
30
30
  withMedia
31
- } from "./chunk-46PFSJZE.js";
31
+ } from "./chunk-AOIY7NGF.js";
32
+ import {
33
+ CompositionOp
34
+ } from "./chunk-KCXADPOC.js";
32
35
 
33
36
  // ../mcp/src/prompts.ts
34
37
  var skillListCache = null;
@@ -74,10 +77,12 @@ var SERVER_INSTRUCTIONS = `You are driving CueFrame \u2014 an API-first, agent-n
74
77
 
75
78
  ## Two lanes (pick per job)
76
79
 
80
+ For \`apply_composition\` edits, call \`describe_composition_ops\` to list operation types, then \`describe_composition_ops({type})\` for only the selected operation's exact JSON Schema. To caption an EDITED timeline, save the clip edits first, then make a SEPARATE \`apply_composition\` call with \`{type:"captions.fromComposition", trackId}\`. It captions all transcribed speech clips on the saved track using placement, trims and exclusions. Clips must have saved duration matching playable source duration divided by playbackRate; otherwise the operation fails with an actionable error. Re-run it after later timeline edits; generated captions are a snapshot. \`captions.fromTranscript\` replaces all captions from ONE media transcript rebased to zero and does not follow a montage. Do not calculate word timing manually.
81
+
77
82
  ORIENT FIRST: call \`get_account\` to see your org, plan, per-feature entitlements, and per-call cost ceilings BEFORE a metered call. READ \`balance\`, NOT \`included\` \u2014 CueFrame is CREDIT-FUNDED, so rendering, generation, preview stills, the judge and the Director carry no separate per-plan allowance: they all spend ONE shared wallet (\`fundedBy\`: \`credits\`; generation is metered in provider-cost units against that wallet), and each \`balance\` restates that same wallet in that feature's own unit. Check the requested cost against the balance. For generation, also check get_account.generationLimit: free accounts have a separate provider-cost cap even with a positive wallet. A billing_required response with reason free_generation_cap requires a credit-pack purchase through the normal checkout flow. Every new org starts with a free credit grant, so an account with NO usage history can still render, generate, preview and score. \`create_render\`, \`compose\`, \`generate_media\`, and \`create_brand_kit\` return 402 \`billing_required\` (the error \`details.featureId\` names it) once the funding balance is exhausted, or when the plan genuinely lacks a non-credit feature. \`get_profile\` is the org's TASTE in one read (brand kits, registered critics + their reference media, recent checkpoint decisions) \u2014 read it before composing for an org you haven't worked with. \`get_usage\` is the metered-usage ledger (per project/job/period); render/compose/score/preview/generate terminals also stamp \`listUsd\` \u2014 the event's LIST price (a plan allowance may zero the invoice line; Autumn/your invoice is the charge authority).
78
83
 
79
84
  **AUTHOR lane \u2014 you bring the plan, CueFrame is the faithful hands (the default):**
80
- \`new_composition\` (mint) \u2192 \`apply_composition\` (add your media clips; overlays/scenes from \`list_catalog\` per each entry's \`placement\`; BRAND/TEXT GRAPHICS: default to authoring your OWN \`{kind:'card', html, tokens}\` clip \u2014 your typography and layout, sized to the format's pixel frame \u2014 then shell it with \`applyMotionPreset\` (persist the card first, shell in the NEXT call). A card's \`tokens\` KEY \`x\` is projected to the CSS custom property \`--cf-x\`, so its html reads \`var(--cf-x)\`: write \`tokens:{"plate":"#0af"}\` + \`background:var(--cf-plate)\`. NEVER put dashes in the key \u2014 \`"--plate"\` becomes \`--cf---plate\`, your \`var(--plate)\` matches nothing, and CSS degrades that SILENTLY to a transparent background with black text (the render still succeeds). \`apply_composition\` returns \`warnings[]\` when it catches this. Catalog text primitives are the quick generic fallback, never the brand-identity path; place with \`region\` + clip-level \`position\`/\`scale\`/\`rotation\`/\`opacity\` \u2014 all of which RENDER; crop intents; captions via \`{type:"captions.fromTranscript", mediaId, window?}\` \u2014 the server slices the cached transcript into sentence-bounded segments, no pasting words \u2014 styled with \`setCaptionStyle\`; \`dry_run:true\` validates without persisting) \u2192 \`validate_composition\` (full dry-run of an INLINE composition; its \`quote\` states the render price. From the ops lane you do not hold the full composition JSON \u2014 use \`apply_composition dry_run:true\` + previews instead, and take prices from get_account/get_usage) \u2192 \`preview_frame\` (defaults to the PERSISTED composition \u2014 exactly what create_render will encode; the response's \`previewed.source\` says so; trust a preview as render-truth ONLY when it is "persisted") \u2192 \`score_composition\` (the standalone sighted judge: per-criterion scores + worst-first critique + a durable kind:'score' Checkpoint) \u2192 fix the lowest criterion \u2192 \`create_render\` \u2192 \`wait_job(kind:"render")\` or webhook \`render.completed\`. Rendering is FAITHFUL to the saved composition \u2014 a golden-tested contract, never a re-interpretation. \`derive_composition\` makes a reframed sibling for another aspect; \`list_compositions\` / \`delete_composition\` manage the set.
85
+ \`new_composition\` (mint) \u2192 \`apply_composition\` (add your media clips; overlays/scenes from \`list_catalog\` per each entry's \`placement\`; BRAND/TEXT GRAPHICS: default to authoring your OWN \`{kind:'card', html, tokens}\` clip \u2014 your typography and layout, sized to the format's pixel frame \u2014 then shell it with \`applyMotionPreset\` (persist the card first, shell in the NEXT call). A card's \`tokens\` KEY \`x\` is projected to the CSS custom property \`--cf-x\`, so its html reads \`var(--cf-x)\`: write \`tokens:{"plate":"#0af"}\` + \`background:var(--cf-plate)\`. NEVER put dashes in the key \u2014 \`"--plate"\` becomes \`--cf---plate\`, your \`var(--plate)\` matches nothing, and CSS degrades that SILENTLY to a transparent background with black text (the render still succeeds). \`apply_composition\` returns \`warnings[]\` when it catches this. Catalog text primitives are the quick generic fallback, never the brand-identity path; place with \`region\` + clip-level \`position\`/\`scale\`/\`rotation\`/\`opacity\` \u2014 all of which RENDER; crop intents; caption edits discovered with \`describe_composition_ops\` and styled with \`setCaptionStyle\`; \`dry_run:true\` validates without persisting) \u2192 \`validate_composition\` (full dry-run of an INLINE composition; its \`quote\` states the render price. From the ops lane you do not hold the full composition JSON \u2014 use \`apply_composition dry_run:true\` + previews instead, and take prices from get_account/get_usage) \u2192 \`preview_frame\` (defaults to the PERSISTED composition \u2014 exactly what create_render will encode; the response's \`previewed.source\` says so; trust a preview as render-truth ONLY when it is "persisted") \u2192 \`score_composition\` (the standalone sighted judge: per-criterion scores + worst-first critique + a durable kind:'score' Checkpoint) \u2192 fix the lowest criterion \u2192 \`create_render\` \u2192 \`wait_job(kind:"render")\` or webhook \`render.completed\`. Rendering is FAITHFUL to the saved composition \u2014 a golden-tested contract, never a re-interpretation. \`derive_composition\` makes a reframed sibling for another aspect; \`list_compositions\` / \`delete_composition\` manage the set.
81
86
 
82
87
  **DIRECTOR lane \u2014 the hosted Director composes FROM your brief (opt in for full authoring):**
83
88
  1. \`create_project\` \u2192 get media in (\`import_media\` / \`generate_media\`; webhook \`media.completed\`) \u2192 \`get_media_context\` (detected faces + transcript \u2014 ground your beats in the real footage).
@@ -441,9 +446,42 @@ function registerCuratedTools(server, ex, opts) {
441
446
  }
442
447
  }));
443
448
  }
449
+ registerCompositionOpDiscovery(server, opts?.onEvent);
444
450
  registerWaitJob(server, ex, opts?.waitCapMs, opts?.onEvent);
445
451
  registerPreviewCard(server, ex, opts?.waitCapMs, opts?.onEvent);
446
452
  }
453
+ function describeCompositionOps(type) {
454
+ const variants = CompositionOp.options;
455
+ const knownTypes = variants.map((variant2) => variant2.shape.type.value);
456
+ if (type === void 0) {
457
+ return {
458
+ operations: variants.map((variant2) => ({
459
+ type: variant2.shape.type.value,
460
+ description: variant2.description ?? ""
461
+ }))
462
+ };
463
+ }
464
+ const variant = variants.find((candidate) => candidate.shape.type.value === type);
465
+ if (!variant) {
466
+ throw new Error(`Unknown CompositionOp type ${JSON.stringify(type)}. Known types: ${knownTypes.join(", ")}`);
467
+ }
468
+ return { type, schema: z.toJSONSchema(variant, { io: "input" }) };
469
+ }
470
+ function registerCompositionOpDiscovery(server, onEvent) {
471
+ server.registerTool("describe_composition_ops", {
472
+ title: "Describe Composition Operations",
473
+ description: "Discover the exact CompositionOp vocabulary accepted by apply_composition. Omit type for a compact list of operation names and descriptions; pass type for only that operation's JSON Schema. Read this before authoring edits or captions.",
474
+ inputSchema: z.object({ type: z.string().optional().describe("Exact CompositionOp type, such as captions.fromComposition. Omit for the operation list.") }),
475
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
476
+ }, async (args) => timed(onEvent, "describe_composition_ops", async () => {
477
+ try {
478
+ const data = describeCompositionOps(args.type);
479
+ return textWithData(JSON.stringify(data), data);
480
+ } catch (error) {
481
+ return errText(fmtErr(error));
482
+ }
483
+ }));
484
+ }
447
485
  function registerPreviewCard(server, ex, waitCapMs, onEvent) {
448
486
  const op = opById("previewFrameJob");
449
487
  server.registerTool(
@@ -567,5 +605,6 @@ export {
567
605
  registerRawTools,
568
606
  titleCase,
569
607
  registerCuratedTools,
608
+ describeCompositionOps,
570
609
  buildServer
571
610
  };
@@ -2,7 +2,7 @@ import {
2
2
  selectAllClips,
3
3
  selectAllOverlays,
4
4
  selectSceneMarkers
5
- } from "./chunk-POTCIQTA.js";
5
+ } from "./chunk-VEQTURKN.js";
6
6
 
7
7
  // ../authoring/src/context/transcript.ts
8
8
  var MAX_TRANSCRIPT_CHARS = 6e4;
@@ -101,7 +101,8 @@ function checkExcludedRanges(track) {
101
101
  if (!ranges || ranges.length === 0) continue;
102
102
  const trim = content.source.trim;
103
103
  const trimStart = trim?.start ?? 0;
104
- const trimEnd = trim?.end ?? trimStart + content.duration;
104
+ const rate = content.source.playbackRate ?? 1;
105
+ const trimEnd = trim?.end ?? trimStart + content.duration * rate;
105
106
  let prevEnd = -Infinity;
106
107
  let total = 0;
107
108
  let earlyExit = false;
@@ -159,18 +160,19 @@ function checkExcludedRanges(track) {
159
160
  }
160
161
  if (earlyExit) continue;
161
162
  const trimSpan = trimEnd - trimStart;
162
- const expectedDuration = trimSpan - total;
163
+ const expectedDuration = (trimSpan - total) / rate;
163
164
  if (Math.abs(expectedDuration - content.duration) > 1e-3) {
164
165
  violations.push({
165
166
  code: "excluded_range_duration_mismatch",
166
- message: `Track ${track.id}: TimelineClip ${content.id} duration (${content.duration}) \u2260 trimSpan (${trimSpan}) \u2212 \u03A3excluded (${total}) = ${expectedDuration}`,
167
+ message: `Track ${track.id}: TimelineClip ${content.id} duration (${content.duration}) \u2260 (trimSpan (${trimSpan}) \u2212 \u03A3excluded (${total})) \xF7 playbackRate (${rate}) = ${expectedDuration}`,
167
168
  details: {
168
169
  trackId: track.id,
169
170
  clipId: content.id,
170
171
  expected: expectedDuration,
171
172
  actual: content.duration,
172
173
  trimSpan,
173
- totalExcluded: total
174
+ totalExcluded: total,
175
+ playbackRate: rate
174
176
  }
175
177
  });
176
178
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  checkTracks
3
- } from "./chunk-DENF4TPI.js";
3
+ } from "./chunk-T56BK7NS.js";
4
4
 
5
5
  // ../composition/src/invariants/etag.ts
6
6
  import { sha256 } from "@noble/hashes/sha256";
@@ -48,12 +48,44 @@ function findMarkerById(composition, id) {
48
48
  }
49
49
  return void 0;
50
50
  }
51
- function trimSourceFor(source, clipRelStart, clipRelEnd) {
51
+ function trimSourceFor(source, clipRelStart, clipRelEnd, clipDuration) {
52
52
  if (source.kind !== "media") return source;
53
53
  const baseStart = source.trim?.start ?? 0;
54
+ const rate = source.playbackRate ?? 1;
55
+ const excluded = source.excludedRanges ?? [];
56
+ const excludedDuration = excluded.reduce((sum, range) => sum + range.end - range.start, 0);
57
+ const baseEnd = source.trim?.end ?? baseStart + clipDuration * rate + excludedDuration;
58
+ const playable = [];
59
+ let cursor = baseStart;
60
+ for (const range of excluded) {
61
+ if (range.start > cursor) playable.push({ start: cursor, end: range.start });
62
+ cursor = Math.max(cursor, range.end);
63
+ }
64
+ if (cursor < baseEnd) playable.push({ start: cursor, end: baseEnd });
65
+ const sourceAt = (outputSeconds, edge) => {
66
+ let remaining = outputSeconds * rate;
67
+ for (let i = 0; i < playable.length; i++) {
68
+ const span = playable[i];
69
+ const length = span.end - span.start;
70
+ if (remaining < length - 1e-9) return span.start + remaining;
71
+ if (Math.abs(remaining - length) <= 1e-9) {
72
+ return edge === "start" && playable[i + 1] ? playable[i + 1].start : span.end;
73
+ }
74
+ remaining -= length;
75
+ }
76
+ return baseEnd;
77
+ };
78
+ const start = sourceAt(clipRelStart, "start");
79
+ const end = sourceAt(clipRelEnd, "end");
80
+ const retained = excluded.flatMap((range) => {
81
+ const clippedStart = Math.max(range.start, start);
82
+ const clippedEnd = Math.min(range.end, end);
83
+ return clippedEnd > clippedStart ? [{ ...range, start: clippedStart, end: clippedEnd }] : [];
84
+ });
54
85
  return {
55
86
  ...source,
56
- trim: { start: baseStart + clipRelStart, end: baseStart + clipRelEnd }
87
+ trim: { start, end },
88
+ ...retained.length > 0 ? { excludedRanges: retained } : { excludedRanges: void 0 }
57
89
  };
58
90
  }
59
91
  function cutTimedRange(start, end, cutStart, cutEnd) {
@@ -234,7 +266,11 @@ function createCompositionOps(get, set) {
234
266
  const oldRate = clip.source.playbackRate ?? 1;
235
267
  if (props.playbackRate !== oldRate) {
236
268
  const trim = clip.source.trim;
237
- const sourceDuration = trim ? trim.end - trim.start : clip.duration * oldRate;
269
+ const excludedDuration = clip.source.excludedRanges?.reduce(
270
+ (sum, range) => sum + range.end - range.start,
271
+ 0
272
+ ) ?? 0;
273
+ const sourceDuration = trim ? trim.end - trim.start - excludedDuration : clip.duration * oldRate;
238
274
  const newDuration = sourceDuration / props.playbackRate;
239
275
  const oldEnd = clip.startTime + clip.duration;
240
276
  const delta = newDuration - clip.duration;
@@ -252,6 +288,16 @@ function createCompositionOps(get, set) {
252
288
  if (props.fit !== void 0) target.fit = props.fit;
253
289
  if (rateChange) {
254
290
  if (target.source.kind === "media") {
291
+ if (!target.source.trim && target.source.excludedRanges?.length) {
292
+ const excludedDuration = target.source.excludedRanges.reduce(
293
+ (sum, range) => sum + range.end - range.start,
294
+ 0
295
+ );
296
+ target.source.trim = {
297
+ start: 0,
298
+ end: target.duration * (target.source.playbackRate ?? 1) + excludedDuration
299
+ };
300
+ }
255
301
  target.source.playbackRate = rateChange.newRate;
256
302
  }
257
303
  target.duration = rateChange.newDuration;
@@ -421,7 +467,7 @@ function createCompositionOps(get, set) {
421
467
  }
422
468
  if (cs < start) {
423
469
  const leftDuration = start - cs;
424
- const leftSource = trimSourceFor(c.source, 0, leftDuration);
470
+ const leftSource = trimSourceFor(c.source, 0, leftDuration, c.duration);
425
471
  next.push({ ...c, id: c.id, duration: leftDuration, source: leftSource });
426
472
  }
427
473
  if (ce > end) {
@@ -430,7 +476,8 @@ function createCompositionOps(get, set) {
430
476
  const rightSource = trimSourceFor(
431
477
  c.source,
432
478
  sourceOffsetIntoOriginal,
433
- sourceOffsetIntoOriginal + rightDuration
479
+ sourceOffsetIntoOriginal + rightDuration,
480
+ c.duration
434
481
  );
435
482
  next.push({
436
483
  ...c,
@@ -472,7 +519,7 @@ function createCompositionOps(get, set) {
472
519
  const before = target.source.excludedRanges ?? [];
473
520
  const beforeTotal = before.reduce((acc, r) => acc + (r.end - r.start), 0);
474
521
  const trimStart = target.source.trim?.start ?? 0;
475
- const trimEnd = target.source.trim?.end ?? trimStart + target.duration + beforeTotal;
522
+ const trimEnd = target.source.trim?.end ?? trimStart + target.duration * (target.source.playbackRate ?? 1) + beforeTotal;
476
523
  const lo = Math.max(sourceStart, trimStart);
477
524
  const hi = Math.min(sourceEnd, trimEnd);
478
525
  if (!(hi > lo)) return;
@@ -510,9 +557,12 @@ function createCompositionOps(get, set) {
510
557
  });
511
558
  }
512
559
  const afterTotal = merged.reduce((acc, r) => acc + (r.end - r.start), 0);
513
- const delta = afterTotal - beforeTotal;
514
- if (delta <= 0) return;
560
+ const addedSourceDuration = afterTotal - beforeTotal;
561
+ if (addedSourceDuration <= 0) return;
562
+ const rate = target.source.playbackRate ?? 1;
563
+ const delta = addedSourceDuration / rate;
515
564
  if (target.duration - delta <= 1e-3) return;
565
+ if (!target.source.trim) target.source.trim = { start: trimStart, end: trimEnd };
516
566
  target.source.excludedRanges = merged;
517
567
  target.duration = target.duration - delta;
518
568
  for (let i = found.clipIndex + 1; i < track.contents.length; i++) {
@@ -522,7 +572,7 @@ function createCompositionOps(get, set) {
522
572
  (acc, r) => acc + Math.max(0, Math.min(r.end, lo) - Math.max(r.start, trimStart)),
523
573
  0
524
574
  );
525
- const tStart = target.startTime + (lo - trimStart) - excludedBeforeLo;
575
+ const tStart = target.startTime + (lo - trimStart - excludedBeforeLo) / rate;
526
576
  const tEnd = tStart + delta;
527
577
  if (draft.composition.markers) {
528
578
  draft.composition.markers = draft.composition.markers.flatMap((m) => {
@@ -551,9 +601,19 @@ function createCompositionOps(get, set) {
551
601
  const ranges = target.source.excludedRanges;
552
602
  if (!ranges || rangeIdx < 0 || rangeIdx >= ranges.length) return;
553
603
  const removed = ranges[rangeIdx];
554
- const restored = removed.end - removed.start;
604
+ const restored = (removed.end - removed.start) / (target.source.playbackRate ?? 1);
555
605
  if (restored <= 0) return;
606
+ const trimStart = target.source.trim?.start ?? 0;
607
+ const excludedBefore = ranges.reduce((sum, range, index) => index === rangeIdx ? sum : sum + Math.max(0, Math.min(range.end, removed.start) - Math.max(range.start, trimStart)), 0);
608
+ const insertAt = target.startTime + (removed.start - trimStart - excludedBefore) / (target.source.playbackRate ?? 1);
556
609
  const next = ranges.filter((_, i) => i !== rangeIdx);
610
+ if (!target.source.trim) {
611
+ const totalExcluded = ranges.reduce((sum, range) => sum + range.end - range.start, 0);
612
+ target.source.trim = {
613
+ start: 0,
614
+ end: target.duration * (target.source.playbackRate ?? 1) + totalExcluded
615
+ };
616
+ }
557
617
  if (next.length === 0) {
558
618
  delete target.source.excludedRanges;
559
619
  } else {
@@ -563,6 +623,24 @@ function createCompositionOps(get, set) {
563
623
  for (let i = found.clipIndex + 1; i < track.contents.length; i++) {
564
624
  track.contents[i].startTime = track.contents[i].startTime + restored;
565
625
  }
626
+ for (const marker of draft.composition.markers ?? []) {
627
+ const shifted = shiftTimedRange(marker.startTime, marker.endTime, insertAt, restored);
628
+ if (shifted) {
629
+ marker.startTime = shifted.start;
630
+ if (shifted.end !== void 0) marker.endTime = shifted.end;
631
+ }
632
+ }
633
+ if (draft.composition.captions) {
634
+ for (const segment of draft.composition.captions.segments) {
635
+ for (const word of segment.words) {
636
+ const shifted = shiftTimedRange(word.startMs, word.endMs, insertAt * 1e3, restored * 1e3);
637
+ if (shifted) {
638
+ word.startMs = shifted.start;
639
+ word.endMs = shifted.end ?? word.endMs;
640
+ }
641
+ }
642
+ }
643
+ }
566
644
  });
567
645
  },
568
646
  setCropIntents: (clipId, segments) => {
@@ -622,7 +700,8 @@ function createCompositionOps(get, set) {
622
700
  ...Object.fromEntries(Object.entries(style).filter(([, v]) => v !== void 0))
623
701
  } : existing?.style;
624
702
  const styleField = mergedStyle && Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {};
625
- draft.composition.captions = derivedFrom ? { source: "derived", from: derivedFrom, segments, ...styleField } : { source: "authored", segments, ...styleField };
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 };
626
705
  });
627
706
  },
628
707
  ripple: (time, amount) => {
@@ -5,7 +5,7 @@ import {
5
5
  isOk,
6
6
  opById,
7
7
  safeParse
8
- } from "./chunk-46PFSJZE.js";
8
+ } from "./chunk-AOIY7NGF.js";
9
9
 
10
10
  // ../mcp/src/wait.ts
11
11
  var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ChatErrorView,
3
3
  DirectorTurnContext
4
- } from "./chunk-4S2YZQJP.js";
4
+ } from "./chunk-N54EGUVM.js";
5
5
  import {
6
6
  GenerationArtifact
7
7
  } from "./chunk-73DQTUWY.js";
package/dist/compose.js CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  import {
55
55
  CueframeError,
56
56
  encodeError
57
- } from "./chunk-ULQJWGUV.js";
57
+ } from "./chunk-E63UEO5C.js";
58
58
  import "./chunk-7PA7FGLD.js";
59
59
  import "./chunk-CL5Y6V6K.js";
60
60
  import "./chunk-2L7PYYJK.js";
@@ -11,7 +11,7 @@ import {
11
11
  otioFrames,
12
12
  otioRationalTime,
13
13
  sourceFrame
14
- } from "../chunk-B4VMU2IJ.js";
14
+ } from "../chunk-TBHSOEYK.js";
15
15
  import {
16
16
  InvariantError,
17
17
  checkClipDurationMatchesTrim,
@@ -22,7 +22,7 @@ import {
22
22
  checkTracks,
23
23
  checkTransitions,
24
24
  isSourceKindCompatible
25
- } from "../chunk-DENF4TPI.js";
25
+ } from "../chunk-T56BK7NS.js";
26
26
  import "../chunk-2ESYSVXG.js";
27
27
  export {
28
28
  InvariantError,
@@ -20,7 +20,7 @@ import {
20
20
  selectMediaClipAt,
21
21
  selectSceneMarkers,
22
22
  selectTracksByKind
23
- } from "../chunk-POTCIQTA.js";
23
+ } from "../chunk-VEQTURKN.js";
24
24
  import "../chunk-AG7HKF2U.js";
25
25
  import "../chunk-ID5QY7V6.js";
26
26
  import "../chunk-WPZKOVYF.js";
@@ -32,7 +32,7 @@ import {
32
32
  silhouetteKeepOutForWindow,
33
33
  sourceRectToOutput,
34
34
  sourceWindowKey
35
- } from "../chunk-EDBQPQAG.js";
35
+ } from "../chunk-FGJ7GKWZ.js";
36
36
  import {
37
37
  BRAND_TOKEN_REFS,
38
38
  BRAND_TOKEN_ROOT_KEYS,
@@ -74,7 +74,7 @@ import {
74
74
  contrastRatio
75
75
  } from "../chunk-ILO4NEZ3.js";
76
76
  import "../chunk-VG2ND6PX.js";
77
- import "../chunk-ULQJWGUV.js";
77
+ import "../chunk-E63UEO5C.js";
78
78
  import "../chunk-AVTEPAWI.js";
79
79
  import "../chunk-2ESYSVXG.js";
80
80
  export {
@@ -26,8 +26,8 @@ import {
26
26
  trackKindForSource,
27
27
  trimForSubRange,
28
28
  valueAt
29
- } from "../chunk-QISFYLBB.js";
30
- import "../chunk-DENF4TPI.js";
29
+ } from "../chunk-IMIUHG2E.js";
30
+ import "../chunk-T56BK7NS.js";
31
31
  import {
32
32
  expandClipIdsToLinkGroups,
33
33
  expandToLinkGroup,