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/bin.js +219 -88
- package/dist/browserEntry.js +1 -1
- package/dist/cli.js +202 -70
- package/dist/index.js +34 -3
- package/dist/labels.js +1 -1
- package/dist/trace-cli.js +1 -1
- package/dist/types/audio.d.ts +15 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ir.d.ts +5 -0
- package/guides/edsl-guide.md +9 -5
- package/package.json +1 -1
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
|
};
|
package/dist/types/audio.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -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";
|
package/guides/edsl-guide.md
CHANGED
|
@@ -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)
|
|
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
|
-
- **
|
|
338
|
-
|
|
339
|
-
`
|
|
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.
|
|
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",
|