reframe-video 0.6.6 → 0.6.7

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/index.js CHANGED
@@ -357,7 +357,7 @@ var PROPS_BY_TYPE = {
357
357
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
358
358
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
359
359
  image: [...COMMON_PROPS, "src", "width", "height", "fit"],
360
- video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
360
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
361
361
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
362
362
  group: COMMON_PROPS
363
363
  };
@@ -2628,11 +2628,34 @@ var SFX_DURATION = {
2628
2628
  thud: 0.25
2629
2629
  };
2630
2630
  var FILE_CUE_DURATION = 0.4;
2631
+ function collectClipAudio(ir, duration, warnings) {
2632
+ const out = [];
2633
+ const walk = (nodes) => {
2634
+ for (const node of nodes) {
2635
+ if (node.type === "video") {
2636
+ const gain = node.props.volume ?? 1;
2637
+ const start = node.props.start ?? 0;
2638
+ if (gain <= 0) continue;
2639
+ if (start >= duration) {
2640
+ warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
2641
+ continue;
2642
+ }
2643
+ out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
2644
+ }
2645
+ if (node.type === "group") walk(node.children);
2646
+ }
2647
+ };
2648
+ walk(ir.nodes);
2649
+ return out;
2650
+ }
2631
2651
  function resolveAudioPlan(compiled) {
2632
2652
  const audio = compiled.ir.audio;
2633
- if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) return null;
2634
2653
  const warnings = [];
2635
2654
  const duration = compiled.duration;
2655
+ const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
2656
+ if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) {
2657
+ return clipAudio.length === 0 ? null : { duration, bgm: null, cues: [], duckWindows: [], clipAudio, warnings };
2658
+ }
2636
2659
  const cues = [];
2637
2660
  for (const [index, cue] of (audio.cues ?? []).entries()) {
2638
2661
  let anchor;
@@ -2668,6 +2691,7 @@ function resolveAudioPlan(compiled) {
2668
2691
  bgm: resolveBgm(audio.bgm),
2669
2692
  cues,
2670
2693
  duckWindows: mergeDuckWindows(cues, duration),
2694
+ clipAudio,
2671
2695
  warnings
2672
2696
  };
2673
2697
  }
@@ -2701,6 +2725,7 @@ function resolveCompositionAudioPlan(comp) {
2701
2725
  const duration = comp.duration;
2702
2726
  const warnings = [];
2703
2727
  const cues = [];
2728
+ const clipAudio = [];
2704
2729
  for (const placement of comp.scenes) {
2705
2730
  const plan = resolveAudioPlan(placement.compiled);
2706
2731
  if (!plan) continue;
@@ -2713,6 +2738,11 @@ function resolveCompositionAudioPlan(comp) {
2713
2738
  if (t >= duration) continue;
2714
2739
  cues.push({ ...cue, t });
2715
2740
  }
2741
+ for (const clip of plan.clipAudio) {
2742
+ const start = clip.start + placement.start;
2743
+ if (start >= duration) continue;
2744
+ clipAudio.push({ ...clip, start });
2745
+ }
2716
2746
  }
2717
2747
  for (const [index, cue] of (audio?.cues ?? []).entries()) {
2718
2748
  if (typeof cue.at !== "number") {
@@ -2732,13 +2762,14 @@ function resolveCompositionAudioPlan(comp) {
2732
2762
  source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
2733
2763
  });
2734
2764
  }
2735
- if (!audio?.bgm && cues.length === 0) return null;
2765
+ if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
2736
2766
  cues.sort((a, b) => a.t - b.t);
2737
2767
  return {
2738
2768
  duration,
2739
2769
  bgm: resolveBgm(audio?.bgm),
2740
2770
  cues,
2741
2771
  duckWindows: mergeDuckWindows(cues, duration),
2772
+ clipAudio,
2742
2773
  warnings
2743
2774
  };
2744
2775
  }
package/dist/labels.js CHANGED
@@ -341,7 +341,7 @@ var PROPS_BY_TYPE = {
341
341
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
342
342
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
343
343
  image: [...COMMON_PROPS, "src", "width", "height", "fit"],
344
- video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
344
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
345
345
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
346
346
  group: COMMON_PROPS
347
347
  };
package/dist/trace-cli.js CHANGED
@@ -14,7 +14,7 @@ var PROPS_BY_TYPE = {
14
14
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
15
15
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
16
16
  image: [...COMMON_PROPS, "src", "width", "height", "fit"],
17
- video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
17
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
18
18
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
19
19
  group: COMMON_PROPS
20
20
  };
@@ -24,6 +24,19 @@ export interface ResolvedCue {
24
24
  path: string;
25
25
  };
26
26
  }
27
+ /** A video node's own audio track, placed on the scene clock. */
28
+ export interface ClipAudio {
29
+ nodeId: string;
30
+ src: string;
31
+ /** Scene-time (s) the clip's audio begins. */
32
+ start: number;
33
+ /** Playback speed (atempo). */
34
+ rate: number;
35
+ /** Source in-point (s) — audio is trimmed to begin here. */
36
+ clipStart: number;
37
+ /** Linear gain. */
38
+ gain: number;
39
+ }
27
40
  export interface AudioPlan {
28
41
  duration: number;
29
42
  bgm: {
@@ -49,6 +62,8 @@ export interface AudioPlan {
49
62
  t0: number;
50
63
  t1: number;
51
64
  }[];
65
+ /** Video clip soundtracks to mux in (a clip with no audio stream is skipped at render). */
66
+ clipAudio: ClipAudio[];
52
67
  warnings: string[];
53
68
  }
54
69
  export declare function resolveAudioPlan(compiled: CompiledScene): AudioPlan | null;
@@ -17,7 +17,7 @@ export { characterPreset, CHARACTER_PRESET_NAMES, type CharacterPresetName, type
17
17
  export { figure, type FigureStyle, type FigureOpts, type FigurePalette } from "./figure.js";
18
18
  export { splitText, textIn, textLoop, textOut, textTypeCues, type SplitOpts, type Glyph, type TextBlock, type FontWeight, type TextInName, type TextLoopName, type TextOutName, type TextLoopOpts, type TextOutOpts, type TypeCueOpts, } from "./textFx.js";
19
19
  export { motionOp, motionOpLabel, MOTION_OPS, type MotionOpName, type MotionOpOpts, type MotionOpResult } from "./motionOps.js";
20
- export { resolveAudioPlan, resolveCompositionAudioPlan, SFX_DURATION, type AudioPlan, type ResolvedCue, } from "./audio.js";
20
+ export { resolveAudioPlan, resolveCompositionAudioPlan, SFX_DURATION, type AudioPlan, type ResolvedCue, type ClipAudio, } from "./audio.js";
21
21
  export { evaluate, sampleProp, nodeParentMatrix, type DisplayList, type DisplayOp, type Mat2D, type ClipRegion, type TextAlign, type TextBaseline, } from "./evaluate.js";
22
22
  export { resolveEase, lerpValue, isColor, EASE_NAMES } from "./interpolate.js";
23
23
  export { sampleBehavior } from "./behaviors.js";
@@ -208,6 +208,11 @@ export interface VideoProps extends BaseProps {
208
208
  rate?: number;
209
209
  /** Source in-point (seconds) shown at `start`. Default 0. */
210
210
  clipStart?: number;
211
+ /**
212
+ * Linear gain for the clip's own audio track, muxed into the output at `start`
213
+ * (trimmed from `clipStart`, sped by `rate`). Default 1; `0` mutes the clip.
214
+ */
215
+ volume?: number;
211
216
  }
212
217
  export type NodeIR = {
213
218
  type: "rect";
@@ -324,19 +324,23 @@ shows the source frame at `clipStart + max(0, t - start) * rate`.
324
324
 
325
325
  ```ts
326
326
  video({ id: "clip", src: "shot.mp4", x: 960, y: 540, width: 1920, height: 1080,
327
- anchor: "center", fit: "cover", start: 0, rate: 1, clipStart: 0 })
327
+ anchor: "center", fit: "cover", start: 0, rate: 1, clipStart: 0, volume: 1 })
328
328
  tween("clip", { scale: 1.08 }, { duration: 5 }) // transform composes with playback (Ken Burns)
329
329
  ```
330
330
 
331
331
  - Props: `src` (mp4 / mov / webm / m4v / mkv, absolute or scene-relative), `width`/`height`,
332
332
  `fit` (`"cover"` like the image node), `start` (scene-time playback begins), `rate`
333
- (speed), `clipStart` (source in-point s). Transform/opacity/effects compose as usual.
333
+ (speed), `clipStart` (source in-point s), `volume` (clip-audio gain, default 1; `0` mutes).
334
+ Transform/opacity/effects compose as usual.
334
335
  - **Deterministic by frame extraction**: render-cli runs `ffmpeg -vf fps=<sceneFps>` to pull
335
336
  the clip's frames, and the renderer draws frame `round(t·fps)` — no live `<video>` seek, so
336
337
  it stays byte-identical (same machine).
337
- - **v1 limitations**: visual-only (the clip's own audio is not muxed use `scene.audio`);
338
- all frames are pre-decoded so keep clips short (≤~5s); like images, not rendered in
339
- `reframe player` / artifacts (mp4 only). See `examples/scenes/video-demo.ts`.
338
+ - **Clip audio**: the clip's own audio track is muxed into the output, placed at `start`
339
+ (trimmed from `clipStart`, sped by `rate`, scaled by `volume`), mixed with `scene.audio`.
340
+ A clip with no audio stream is skipped; set `volume: 0` to drop a clip's sound.
341
+ - **Limitations**: all frames are pre-decoded so keep clips short (≤~5s); like images, video
342
+ sources are not rendered in `reframe player` / artifacts (mp4 only). See
343
+ `examples/scenes/video-demo.ts`.
340
344
 
341
345
  ## Cursor (UI demos)
342
346
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
5
5
  "keywords": [
6
6
  "motion-graphics",