Setting the file. One moment.
Duck · Media Use · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page ⋯
scripts/11 files
Script
scripts/lib/ duck.mjs
JavaScript · 95 lines · 3 KB
lists
=
wordListsFromMediaMeta
(meta);
7 const voices = Array. isArray (meta?.voices) ? meta.voices : [];
8 if (lists. length > 1 && ! offsets && ! sequential) {
9 throw new Error (
10 "audio_meta has multiple voice lines with file-relative times; pass --sequential or --offsets so spans land at composition time" ,
11 );
12 }
13 const intervals = [];
14 let cursor = 0 ;
15 for ( let i = 0 ; i < lists. length ; i ++ ) {
16 const voice = voices[i];
17 let offset = 0 ;
18 if (offsets) {
19 const id = voice?.id ?? String (i);
20 if ( ! (id in offsets)) throw new Error ( `--offsets is missing voice "${ id }"` );
21 offset = Number (offsets[id]) || 0 ;
22 } else if (sequential) {
23 offset = cursor;
24 const lineDuration = Number (voice?.duration_s) || Math. max ( ... lists[i]. map (( w ) => w.end), 0 );
25 cursor += lineDuration + ( Number (gap) || 0 );
26 }
27 for ( const word of lists[i]) {
28 if (word.end > word.start)
29 intervals. push ({ start: word.start + offset, end: word.end + offset });
30 }
31 }
32 return mergeIntervals (intervals, Number. isFinite (merge) && merge >= 0 ? merge : 0.6 );
33 }
34
35 export function duckKeyframes (
36 spans ,
37 { duck = 0.25 , attack = 0.15 , release = 0.4 , baseVolume = 1 } = {},
38 ) {
39 const base = finiteOr (baseVolume, 1 );
40 const ducked = round3 (base * finiteOr (duck, 0.25 ));
41 const keyframes = [];
42 for ( const span of spans) {
43 keyframes. push ({
44 time: round3 (Math. max ( 0 , finiteOr (span.start, 0 ))),
45 volume: ducked,
46 duration: round3 ( finiteOr (attack, 0.15 )),
47 });
48 keyframes. push ({
49 time: round3 (Math. max ( 0 , finiteOr (span.end, 0 ))),
50 volume: round3 (base),
51 duration: round3 ( finiteOr (release, 0.4 )),
52 });
53 }
54 return keyframes. sort (( a , b ) => a.time - b.time);
55 }
56
57 /** Volume lane for `data-automation`: composition-time keyframes as clip-local ramps. */
58 export function duckLane ( keyframes , { clipStart = 0 , baseVolume = 1 } = {}) {
59 const start = finiteOr (clipStart, 0 );
60 const points = [{ t: 0 , v: round3 ( finiteOr (baseVolume, 1 )) }];
61 const push = ( t , v ) => {
62 if (t > points. at ( - 1 ).t) points. push ({ t: round3 (t), v });
63 };
64 for ( const kf of keyframes) {
65 const t = Math. max ( 0 , kf.time - start);
66 push (t, points. at ( - 1 ).v);
67 push (t + kf.duration, kf.volume);
68 }
69 return { version: 1 , lanes: [{ target: "volume" , points }] };
70 }
71
72 function mergeIntervals ( intervals , mergeGap ) {
73 const sorted = intervals
74 . map (( range ) => ({ start: round3 (range.start), end: round3 (range.end) }))
75 . sort (( a , b ) => a.start - b.start || a.end - b.end);
76 const merged = [];
77 for ( const range of sorted) {
78 const prev = merged. at ( - 1 );
79 if (prev && (range.start <= prev.end || range.start - prev.end < mergeGap)) {
80 prev.end = Math. max (prev.end, range.end);
81 } else {
82 merged. push ({ ... range });
83 }
84 }
85 return merged;
86 }
87
88 function finiteOr ( value , fallback ) {
89 const n = Number (value);
90 return Number. isFinite (n) ? n : fallback;
91 }
92
93 function round3 ( n ) {
94 return Math. round ( Number (n) * 1000 ) / 1000 ;
95 }