reframe-video 0.6.17 → 0.6.19
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/README.md +45 -0
- package/dist/bin.js +120 -34
- package/dist/browserEntry.js +1 -1
- package/dist/cli.js +98 -28
- package/dist/compile-api.d.ts +6 -0
- package/dist/compile-api.js +477 -0
- package/dist/compile.js +477 -0
- package/dist/diff.js +77 -26
- package/dist/index.js +17 -2
- package/dist/labels.js +81 -32
- package/dist/renderer-canvas.d.ts +1 -0
- package/dist/renderer-canvas.js +1 -1
- package/dist/trace-cli.js +1 -1
- package/dist/types/audio.d.ts +10 -0
- package/dist/types/ir.d.ts +10 -0
- package/dist/types-renderer/index.d.ts +34 -0
- package/guides/edsl-guide.md +7 -3
- package/package.json +11 -2
package/dist/labels.js
CHANGED
|
@@ -349,7 +349,7 @@ var PROPS_BY_TYPE = {
|
|
|
349
349
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
350
350
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
351
351
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
352
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
352
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
353
353
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
354
354
|
group: COMMON_PROPS
|
|
355
355
|
};
|
|
@@ -586,6 +586,15 @@ function validateScene(ir) {
|
|
|
586
586
|
if (cue.gain !== void 0 && cue.gain < 0) {
|
|
587
587
|
problems.push(`audio.cues[${i}]: gain must be >= 0`);
|
|
588
588
|
}
|
|
589
|
+
if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
|
|
590
|
+
problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
|
|
591
|
+
}
|
|
592
|
+
if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
|
|
593
|
+
problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
|
|
594
|
+
}
|
|
595
|
+
if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
|
|
596
|
+
problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
|
|
597
|
+
}
|
|
589
598
|
}
|
|
590
599
|
const duck = ir.audio?.bgm?.duck;
|
|
591
600
|
if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
|
|
@@ -669,42 +678,76 @@ import { dirname, resolve } from "node:path";
|
|
|
669
678
|
import { fileURLToPath } from "node:url";
|
|
670
679
|
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
671
680
|
var CORE_ENTRY = true ? resolve(HERE, "index.js") : resolve(HERE, "..", "..", "core", "src", "index.ts");
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
681
|
+
var SceneLoadError = class extends Error {
|
|
682
|
+
kind;
|
|
683
|
+
constructor(kind, message, options) {
|
|
684
|
+
super(message, options);
|
|
685
|
+
this.name = "SceneLoadError";
|
|
686
|
+
this.kind = kind;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
var clean = (err) => (err instanceof Error ? err.message : String(err)).replace(
|
|
690
|
+
/data:text\/javascript;base64,[A-Za-z0-9+/=]+/g,
|
|
691
|
+
"<scene bundle>"
|
|
692
|
+
);
|
|
693
|
+
var ALIAS = { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY };
|
|
694
|
+
async function bundle(input) {
|
|
695
|
+
const common = {
|
|
696
|
+
bundle: true,
|
|
697
|
+
format: "esm",
|
|
698
|
+
platform: "neutral",
|
|
699
|
+
write: false,
|
|
700
|
+
logLevel: "silent",
|
|
701
|
+
sourcemap: "inline",
|
|
702
|
+
alias: ALIAS
|
|
703
|
+
};
|
|
675
704
|
try {
|
|
676
|
-
const out = await build(
|
|
677
|
-
entryPoints: [
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
});
|
|
688
|
-
code = out.outputFiles[0].text;
|
|
705
|
+
const out = await build(
|
|
706
|
+
"path" in input ? { ...common, entryPoints: [input.path] } : { ...common, stdin: { contents: input.code, resolveDir: input.resolveDir, loader: "ts", sourcefile: "scene.ts" } }
|
|
707
|
+
);
|
|
708
|
+
return out.outputFiles[0].text;
|
|
709
|
+
} catch (err) {
|
|
710
|
+
throw new SceneLoadError("bundle", clean(err), { cause: err });
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async function importDefault(code, label) {
|
|
714
|
+
let mod;
|
|
715
|
+
try {
|
|
716
|
+
mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
|
|
689
717
|
} catch (err) {
|
|
690
|
-
|
|
691
|
-
|
|
718
|
+
const kind = err instanceof Error && err.name === "SceneValidationError" ? "validation" : "eval";
|
|
719
|
+
throw new SceneLoadError(kind, clean(err), { cause: err });
|
|
692
720
|
}
|
|
693
|
-
|
|
694
|
-
if (mod.default === void 0) throw new Error(`${path3} must default-export a scene or composition`);
|
|
721
|
+
if (mod.default === void 0) throw new SceneLoadError("eval", `${label} must default-export a scene or composition`);
|
|
695
722
|
return mod.default;
|
|
696
723
|
}
|
|
724
|
+
async function loadDefault(path3) {
|
|
725
|
+
if (path3.endsWith(".json")) {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(await readFile(path3, "utf8"));
|
|
728
|
+
} catch (err) {
|
|
729
|
+
throw new SceneLoadError("eval", `failed to read ${path3}: ${clean(err)}`, { cause: err });
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return importDefault(await bundle({ path: path3 }), path3);
|
|
733
|
+
}
|
|
697
734
|
function isComposition(def) {
|
|
698
735
|
return typeof def === "object" && def !== null && Array.isArray(def.scenes);
|
|
699
736
|
}
|
|
700
|
-
|
|
701
|
-
const def = await loadDefault(path3);
|
|
737
|
+
function asScene(def, label) {
|
|
702
738
|
if (isComposition(def)) {
|
|
703
|
-
throw new
|
|
739
|
+
throw new SceneLoadError("validation", `${label} is a composition \u2014 render it directly, not as a single scene`);
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
validateScene(def);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
throw new SceneLoadError("validation", clean(err), { cause: err });
|
|
704
745
|
}
|
|
705
|
-
validateScene(def);
|
|
706
746
|
return def;
|
|
707
747
|
}
|
|
748
|
+
async function loadScene(path3) {
|
|
749
|
+
return asScene(await loadDefault(path3), path3);
|
|
750
|
+
}
|
|
708
751
|
|
|
709
752
|
// ../render-cli/src/labels.ts
|
|
710
753
|
var path2 = process.argv[2];
|
|
@@ -712,11 +755,17 @@ if (!path2) {
|
|
|
712
755
|
console.error("usage: reframe labels <scene.ts|.json>");
|
|
713
756
|
process.exit(1);
|
|
714
757
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
console.log(`# ${
|
|
720
|
-
|
|
721
|
-
|
|
758
|
+
async function main() {
|
|
759
|
+
const scene = await loadScene(path2);
|
|
760
|
+
const compiled = compileScene(scene);
|
|
761
|
+
const rows = [...compiled.labelTimes.entries()].sort((a, b) => a[1].t0 - b[1].t0 || a[0].localeCompare(b[0]));
|
|
762
|
+
console.log(`# ${scene.id} \u2014 ${rows.length} labels \xB7 ${compiled.duration.toFixed(2)}s @ ${scene.fps ?? 30}fps`);
|
|
763
|
+
console.log(`# ${"start".padStart(7)} ${"end".padStart(7)} label`);
|
|
764
|
+
for (const [name, { t0, t1 }] of rows) {
|
|
765
|
+
console.log(`${`${t0.toFixed(2)}s`.padStart(8)} ${`${t1.toFixed(2)}s`.padStart(8)} ${name}`);
|
|
766
|
+
}
|
|
722
767
|
}
|
|
768
|
+
main().catch((err) => {
|
|
769
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types-renderer/index.js";
|
package/dist/renderer-canvas.js
CHANGED
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", "prefix", "suffix", "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", "volume"],
|
|
17
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
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
|
@@ -15,6 +15,12 @@ export interface ResolvedCue {
|
|
|
15
15
|
t: number;
|
|
16
16
|
gain: number;
|
|
17
17
|
duration: number;
|
|
18
|
+
/** Fade in over N seconds from the cue start (0 = none). */
|
|
19
|
+
fadeIn: number;
|
|
20
|
+
/** Fade out over N seconds before the cue end (0 = none). */
|
|
21
|
+
fadeOut: number;
|
|
22
|
+
/** Stereo balance: -1 left … 0 centre … +1 right. */
|
|
23
|
+
pan: number;
|
|
18
24
|
source: {
|
|
19
25
|
kind: "sfx";
|
|
20
26
|
name: SfxName;
|
|
@@ -36,6 +42,10 @@ export interface ClipAudio {
|
|
|
36
42
|
clipStart: number;
|
|
37
43
|
/** Linear gain. */
|
|
38
44
|
gain: number;
|
|
45
|
+
/** Fade in over N seconds from the clip's `start` (0 = none). */
|
|
46
|
+
fadeIn: number;
|
|
47
|
+
/** Stereo balance: -1 left … 0 centre … +1 right. */
|
|
48
|
+
pan: number;
|
|
39
49
|
}
|
|
40
50
|
export interface AudioPlan {
|
|
41
51
|
duration: number;
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -256,6 +256,10 @@ export interface VideoProps extends BaseProps {
|
|
|
256
256
|
* (trimmed from `clipStart`, sped by `rate`). Default 1; `0` mutes the clip.
|
|
257
257
|
*/
|
|
258
258
|
volume?: number;
|
|
259
|
+
/** Fade the clip audio in over N seconds from `start` (default 0 = hard in). */
|
|
260
|
+
fadeIn?: number;
|
|
261
|
+
/** Stereo balance for the clip audio: -1 full left, 0 centre, +1 full right. */
|
|
262
|
+
pan?: number;
|
|
259
263
|
}
|
|
260
264
|
export type NodeIR = {
|
|
261
265
|
type: "rect";
|
|
@@ -420,6 +424,12 @@ export interface AudioCueIR {
|
|
|
420
424
|
file?: string;
|
|
421
425
|
/** Linear gain, default 1. */
|
|
422
426
|
gain?: number;
|
|
427
|
+
/** Fade the cue in over N seconds from its start (default 0 = hard in). */
|
|
428
|
+
fadeIn?: number;
|
|
429
|
+
/** Fade the cue out over N seconds before its end (default 0 = hard out). */
|
|
430
|
+
fadeOut?: number;
|
|
431
|
+
/** Stereo balance: -1 full left, 0 centre (default), +1 full right. */
|
|
432
|
+
pan?: number;
|
|
423
433
|
/** Synth parameter overrides (seed, duration, …) — numbers only. */
|
|
424
434
|
params?: Record<string, number>;
|
|
425
435
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DisplayList -> Canvas 2D. Drawing only — all animation math lives in
|
|
3
|
+
* @reframe/core's evaluate(). Restricted to the plain Canvas 2D API so the
|
|
4
|
+
* same code runs in the browser (preview), under Playwright (export), and
|
|
5
|
+
* could port to skia-canvas later.
|
|
6
|
+
*/
|
|
7
|
+
import type { CompiledScene, DisplayList, SceneIR } from "../types/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Decoded images keyed by the RAW src string from the IR (never a resolved
|
|
10
|
+
* path/URL — the DisplayList stays machine-independent). Consumers populate
|
|
11
|
+
* it before the first frame; a plain Map satisfies the interface.
|
|
12
|
+
*/
|
|
13
|
+
export interface ImageRegistry {
|
|
14
|
+
get(src: string): CanvasImageSource | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Decoded video frames keyed by the RAW src string + frame index. A video is
|
|
18
|
+
* rendered as a frame sequence (extracted at the scene fps): `frame(src, i)`
|
|
19
|
+
* returns the i-th source frame, clamped to the available range by the consumer.
|
|
20
|
+
*/
|
|
21
|
+
export interface VideoRegistry {
|
|
22
|
+
frame(src: string, index: number): CanvasImageSource | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare function renderFrame(ctx: CanvasRenderingContext2D, compiled: CompiledScene, t: number, images?: ImageRegistry, videos?: VideoRegistry): void;
|
|
25
|
+
export declare function drawDisplayList(ctx: CanvasRenderingContext2D, ops: DisplayList, images?: ImageRegistry, videos?: VideoRegistry): void;
|
|
26
|
+
/** Center cover-crop: the source rect (in image pixels) that fills a dw×dh box at
|
|
27
|
+
* the image's natural aspect — the larger axis is cropped equally on both sides. */
|
|
28
|
+
export declare function coverRect(iw: number, ih: number, dw: number, dh: number): {
|
|
29
|
+
sx: number;
|
|
30
|
+
sy: number;
|
|
31
|
+
sw: number;
|
|
32
|
+
sh: number;
|
|
33
|
+
};
|
|
34
|
+
export type { SceneIR };
|
package/guides/edsl-guide.md
CHANGED
|
@@ -510,15 +510,19 @@ Label-anchored sound design — cues follow retiming and regeneration:
|
|
|
510
510
|
audio: {
|
|
511
511
|
bgm: { synth: "ambient-pad", gain: 0.3, fadeIn: 1, fadeOut: 2, duck: { depth: 0.5 } },
|
|
512
512
|
cues: [
|
|
513
|
-
{ at: "enter", sfx: "whoosh", gain: 0.8 },
|
|
514
|
-
{ at: "enter", offset: 0.2, sfx: "pop" },
|
|
515
|
-
{ at: 5.0, file: "keypress-001.wav", gain: 0.5 },
|
|
513
|
+
{ at: "enter", sfx: "whoosh", gain: 0.8, pan: -0.6 }, // anchored to a label; panned left
|
|
514
|
+
{ at: "enter", offset: 0.2, sfx: "pop", fadeIn: 0.05, fadeOut: 0.1 },
|
|
515
|
+
{ at: 5.0, file: "keypress-001.wav", gain: 0.5 }, // absolute seconds; file from assets/sfx/
|
|
516
516
|
],
|
|
517
517
|
}
|
|
518
518
|
```
|
|
519
519
|
|
|
520
520
|
Procedural sfx names: `whoosh` `pop` `tick` `rise` `shimmer` `thud` (deterministic,
|
|
521
521
|
seedable via `params: { seed }`). Exactly one of `sfx`/`file` per cue.
|
|
522
|
+
**Mixing**: any cue takes `fadeIn`/`fadeOut` (seconds) and `pan` (-1 left … 0 centre …
|
|
523
|
+
+1 right). A `video` clip's audio takes `fadeIn` and `pan` too (clip fade-out isn't
|
|
524
|
+
supported yet — a clip has no fixed length in the plan). The bed auto-ducks under cues
|
|
525
|
+
(`bgm.duck`).
|
|
522
526
|
|
|
523
527
|
## Rules
|
|
524
528
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.19",
|
|
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",
|
|
@@ -28,7 +28,16 @@
|
|
|
28
28
|
".": {
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"import": "./dist/index.js"
|
|
31
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"./renderer": {
|
|
33
|
+
"types": "./dist/renderer-canvas.d.ts",
|
|
34
|
+
"import": "./dist/renderer-canvas.js"
|
|
35
|
+
},
|
|
36
|
+
"./compile": {
|
|
37
|
+
"types": "./dist/compile-api.d.ts",
|
|
38
|
+
"import": "./dist/compile-api.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
32
41
|
},
|
|
33
42
|
"files": [
|
|
34
43
|
"dist",
|