reframe-video 0.6.17 → 0.6.18
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 +28 -3
- package/dist/browserEntry.js +1 -1
- package/dist/cli.js +31 -3
- package/dist/diff.js +10 -1
- package/dist/index.js +17 -2
- package/dist/labels.js +10 -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/guides/edsl-guide.md +7 -3
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -578,6 +578,15 @@ function validateScene(ir) {
|
|
|
578
578
|
if (cue.gain !== void 0 && cue.gain < 0) {
|
|
579
579
|
problems.push(`audio.cues[${i}]: gain must be >= 0`);
|
|
580
580
|
}
|
|
581
|
+
if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
|
|
582
|
+
problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
|
|
583
|
+
}
|
|
584
|
+
if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
|
|
585
|
+
problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
|
|
586
|
+
}
|
|
587
|
+
if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
|
|
588
|
+
problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
|
|
589
|
+
}
|
|
581
590
|
}
|
|
582
591
|
const duck = ir.audio?.bgm?.duck;
|
|
583
592
|
if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
|
|
@@ -644,7 +653,7 @@ var init_validate = __esm({
|
|
|
644
653
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
645
654
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
646
655
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
647
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
656
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
648
657
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
649
658
|
group: COMMON_PROPS
|
|
650
659
|
};
|
|
@@ -1251,7 +1260,7 @@ function collectClipAudio(ir, duration, warnings) {
|
|
|
1251
1260
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
1252
1261
|
continue;
|
|
1253
1262
|
}
|
|
1254
|
-
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
|
|
1263
|
+
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain, fadeIn: node.props.fadeIn ?? 0, pan: node.props.pan ?? 0 });
|
|
1255
1264
|
}
|
|
1256
1265
|
if (node.type === "group") walk(node.children);
|
|
1257
1266
|
}
|
|
@@ -1293,6 +1302,9 @@ function resolveAudioPlan(compiled) {
|
|
|
1293
1302
|
t,
|
|
1294
1303
|
gain: cue.gain ?? 1,
|
|
1295
1304
|
duration: cueDuration,
|
|
1305
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
1306
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
1307
|
+
pan: cue.pan ?? 0,
|
|
1296
1308
|
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
1297
1309
|
});
|
|
1298
1310
|
}
|
|
@@ -1985,6 +1997,10 @@ function atempoChain(rate) {
|
|
|
1985
1997
|
out.push(`atempo=${r.toFixed(4)}`);
|
|
1986
1998
|
return out;
|
|
1987
1999
|
}
|
|
2000
|
+
function panFilter(pan) {
|
|
2001
|
+
const clamp = (v) => Math.max(0, Math.min(1, v)).toFixed(4);
|
|
2002
|
+
return `pan=stereo|c0=${clamp(1 - pan)}*c0|c1=${clamp(1 + pan)}*c1`;
|
|
2003
|
+
}
|
|
1988
2004
|
function buildFilterGraph(plan, inputs) {
|
|
1989
2005
|
const lines = [];
|
|
1990
2006
|
const mixIn = ["[anchor]"];
|
|
@@ -2013,7 +2029,14 @@ function buildFilterGraph(plan, inputs) {
|
|
|
2013
2029
|
}
|
|
2014
2030
|
plan.cues.forEach((cue, i) => {
|
|
2015
2031
|
const delayMs = Math.round(cue.t * 1e3);
|
|
2016
|
-
|
|
2032
|
+
const chain = [FORMAT, `volume=${cue.gain}`];
|
|
2033
|
+
if (cue.fadeIn > 0) chain.push(`afade=t=in:st=0:d=${cue.fadeIn}`);
|
|
2034
|
+
if (cue.fadeOut > 0) {
|
|
2035
|
+
chain.push(`afade=t=out:st=${Math.max(0, cue.duration - cue.fadeOut).toFixed(3)}:d=${cue.fadeOut}`);
|
|
2036
|
+
}
|
|
2037
|
+
if (cue.pan !== 0) chain.push(panFilter(cue.pan));
|
|
2038
|
+
chain.push(`adelay=${delayMs}:all=1`);
|
|
2039
|
+
lines.push(`[${inputIndex}:a]${chain.join(",")}[c${i}]`);
|
|
2017
2040
|
mixIn.push(`[c${i}]`);
|
|
2018
2041
|
inputIndex++;
|
|
2019
2042
|
});
|
|
@@ -2021,6 +2044,8 @@ function buildFilterGraph(plan, inputs) {
|
|
|
2021
2044
|
const chain = [];
|
|
2022
2045
|
if (audio.clipStart > 0) chain.push(`atrim=start=${audio.clipStart.toFixed(3)}`, "asetpts=PTS-STARTPTS");
|
|
2023
2046
|
chain.push(...atempoChain(audio.rate), FORMAT, `volume=${audio.gain}`);
|
|
2047
|
+
if (audio.fadeIn > 0) chain.push(`afade=t=in:st=0:d=${audio.fadeIn}`);
|
|
2048
|
+
if (audio.pan !== 0) chain.push(panFilter(audio.pan));
|
|
2024
2049
|
const delayMs = Math.round(audio.start * 1e3);
|
|
2025
2050
|
if (delayMs > 0) chain.push(`adelay=${delayMs}:all=1`);
|
|
2026
2051
|
lines.push(`[${inputIndex}:a]${chain.join(",")}[k${i}]`);
|
package/dist/browserEntry.js
CHANGED
|
@@ -350,7 +350,7 @@
|
|
|
350
350
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
351
351
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
352
352
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
353
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
353
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
354
354
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
355
355
|
group: COMMON_PROPS
|
|
356
356
|
};
|
package/dist/cli.js
CHANGED
|
@@ -355,7 +355,7 @@ var PROPS_BY_TYPE = {
|
|
|
355
355
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
356
356
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
357
357
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
358
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
358
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
359
359
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
360
360
|
group: COMMON_PROPS
|
|
361
361
|
};
|
|
@@ -592,6 +592,15 @@ function validateScene(ir) {
|
|
|
592
592
|
if (cue.gain !== void 0 && cue.gain < 0) {
|
|
593
593
|
problems.push(`audio.cues[${i}]: gain must be >= 0`);
|
|
594
594
|
}
|
|
595
|
+
if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
|
|
596
|
+
problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
|
|
597
|
+
}
|
|
598
|
+
if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
|
|
599
|
+
problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
|
|
600
|
+
}
|
|
601
|
+
if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
|
|
602
|
+
problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
|
|
603
|
+
}
|
|
595
604
|
}
|
|
596
605
|
const duck = ir.audio?.bgm?.duck;
|
|
597
606
|
if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
|
|
@@ -919,7 +928,7 @@ function collectClipAudio(ir, duration, warnings) {
|
|
|
919
928
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
920
929
|
continue;
|
|
921
930
|
}
|
|
922
|
-
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
|
|
931
|
+
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain, fadeIn: node.props.fadeIn ?? 0, pan: node.props.pan ?? 0 });
|
|
923
932
|
}
|
|
924
933
|
if (node.type === "group") walk(node.children);
|
|
925
934
|
}
|
|
@@ -961,6 +970,9 @@ function resolveAudioPlan(compiled) {
|
|
|
961
970
|
t,
|
|
962
971
|
gain: cue.gain ?? 1,
|
|
963
972
|
duration: cueDuration,
|
|
973
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
974
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
975
|
+
pan: cue.pan ?? 0,
|
|
964
976
|
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
965
977
|
});
|
|
966
978
|
}
|
|
@@ -1038,6 +1050,9 @@ function resolveCompositionAudioPlan(comp) {
|
|
|
1038
1050
|
t,
|
|
1039
1051
|
gain: cue.gain ?? 1,
|
|
1040
1052
|
duration: cueDuration,
|
|
1053
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
1054
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
1055
|
+
pan: cue.pan ?? 0,
|
|
1041
1056
|
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
1042
1057
|
});
|
|
1043
1058
|
}
|
|
@@ -1457,6 +1472,10 @@ function atempoChain(rate) {
|
|
|
1457
1472
|
out.push(`atempo=${r.toFixed(4)}`);
|
|
1458
1473
|
return out;
|
|
1459
1474
|
}
|
|
1475
|
+
function panFilter(pan) {
|
|
1476
|
+
const clamp = (v) => Math.max(0, Math.min(1, v)).toFixed(4);
|
|
1477
|
+
return `pan=stereo|c0=${clamp(1 - pan)}*c0|c1=${clamp(1 + pan)}*c1`;
|
|
1478
|
+
}
|
|
1460
1479
|
function buildFilterGraph(plan, inputs) {
|
|
1461
1480
|
const lines = [];
|
|
1462
1481
|
const mixIn = ["[anchor]"];
|
|
@@ -1485,7 +1504,14 @@ function buildFilterGraph(plan, inputs) {
|
|
|
1485
1504
|
}
|
|
1486
1505
|
plan.cues.forEach((cue, i) => {
|
|
1487
1506
|
const delayMs = Math.round(cue.t * 1e3);
|
|
1488
|
-
|
|
1507
|
+
const chain = [FORMAT, `volume=${cue.gain}`];
|
|
1508
|
+
if (cue.fadeIn > 0) chain.push(`afade=t=in:st=0:d=${cue.fadeIn}`);
|
|
1509
|
+
if (cue.fadeOut > 0) {
|
|
1510
|
+
chain.push(`afade=t=out:st=${Math.max(0, cue.duration - cue.fadeOut).toFixed(3)}:d=${cue.fadeOut}`);
|
|
1511
|
+
}
|
|
1512
|
+
if (cue.pan !== 0) chain.push(panFilter(cue.pan));
|
|
1513
|
+
chain.push(`adelay=${delayMs}:all=1`);
|
|
1514
|
+
lines.push(`[${inputIndex}:a]${chain.join(",")}[c${i}]`);
|
|
1489
1515
|
mixIn.push(`[c${i}]`);
|
|
1490
1516
|
inputIndex++;
|
|
1491
1517
|
});
|
|
@@ -1493,6 +1519,8 @@ function buildFilterGraph(plan, inputs) {
|
|
|
1493
1519
|
const chain = [];
|
|
1494
1520
|
if (audio.clipStart > 0) chain.push(`atrim=start=${audio.clipStart.toFixed(3)}`, "asetpts=PTS-STARTPTS");
|
|
1495
1521
|
chain.push(...atempoChain(audio.rate), FORMAT, `volume=${audio.gain}`);
|
|
1522
|
+
if (audio.fadeIn > 0) chain.push(`afade=t=in:st=0:d=${audio.fadeIn}`);
|
|
1523
|
+
if (audio.pan !== 0) chain.push(panFilter(audio.pan));
|
|
1496
1524
|
const delayMs = Math.round(audio.start * 1e3);
|
|
1497
1525
|
if (delayMs > 0) chain.push(`adelay=${delayMs}:all=1`);
|
|
1498
1526
|
lines.push(`[${inputIndex}:a]${chain.join(",")}[k${i}]`);
|
package/dist/diff.js
CHANGED
|
@@ -361,7 +361,7 @@ var PROPS_BY_TYPE = {
|
|
|
361
361
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
362
362
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
363
363
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
364
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
364
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
365
365
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
366
366
|
group: COMMON_PROPS
|
|
367
367
|
};
|
|
@@ -598,6 +598,15 @@ function validateScene(ir) {
|
|
|
598
598
|
if (cue.gain !== void 0 && cue.gain < 0) {
|
|
599
599
|
problems.push(`audio.cues[${i}]: gain must be >= 0`);
|
|
600
600
|
}
|
|
601
|
+
if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
|
|
602
|
+
problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
|
|
603
|
+
}
|
|
604
|
+
if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
|
|
605
|
+
problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
|
|
606
|
+
}
|
|
607
|
+
if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
|
|
608
|
+
problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
|
|
609
|
+
}
|
|
601
610
|
}
|
|
602
611
|
const duck = ir.audio?.bgm?.duck;
|
|
603
612
|
if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
|
package/dist/index.js
CHANGED
|
@@ -365,7 +365,7 @@ var PROPS_BY_TYPE = {
|
|
|
365
365
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
366
366
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
367
367
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
368
|
-
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
368
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
|
|
369
369
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
370
370
|
group: COMMON_PROPS
|
|
371
371
|
};
|
|
@@ -602,6 +602,15 @@ function validateScene(ir) {
|
|
|
602
602
|
if (cue.gain !== void 0 && cue.gain < 0) {
|
|
603
603
|
problems.push(`audio.cues[${i}]: gain must be >= 0`);
|
|
604
604
|
}
|
|
605
|
+
if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
|
|
606
|
+
problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
|
|
607
|
+
}
|
|
608
|
+
if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
|
|
609
|
+
problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
|
|
610
|
+
}
|
|
611
|
+
if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
|
|
612
|
+
problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
|
|
613
|
+
}
|
|
605
614
|
}
|
|
606
615
|
const duck = ir.audio?.bgm?.duck;
|
|
607
616
|
if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
|
|
@@ -2690,7 +2699,7 @@ function collectClipAudio(ir, duration, warnings) {
|
|
|
2690
2699
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
2691
2700
|
continue;
|
|
2692
2701
|
}
|
|
2693
|
-
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
|
|
2702
|
+
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain, fadeIn: node.props.fadeIn ?? 0, pan: node.props.pan ?? 0 });
|
|
2694
2703
|
}
|
|
2695
2704
|
if (node.type === "group") walk(node.children);
|
|
2696
2705
|
}
|
|
@@ -2732,6 +2741,9 @@ function resolveAudioPlan(compiled) {
|
|
|
2732
2741
|
t,
|
|
2733
2742
|
gain: cue.gain ?? 1,
|
|
2734
2743
|
duration: cueDuration,
|
|
2744
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
2745
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
2746
|
+
pan: cue.pan ?? 0,
|
|
2735
2747
|
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
2736
2748
|
});
|
|
2737
2749
|
}
|
|
@@ -2809,6 +2821,9 @@ function resolveCompositionAudioPlan(comp) {
|
|
|
2809
2821
|
t,
|
|
2810
2822
|
gain: cue.gain ?? 1,
|
|
2811
2823
|
duration: cueDuration,
|
|
2824
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
2825
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
2826
|
+
pan: cue.pan ?? 0,
|
|
2812
2827
|
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
2813
2828
|
});
|
|
2814
2829
|
}
|
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)) {
|
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
|
}
|
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.18",
|
|
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",
|