@hyperframes/engine 0.6.55 → 0.6.57

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.
@@ -1 +1 @@
1
- {"version":3,"file":"audioMixer.d.ts","sourceRoot":"","sources":["../../src/services/audioMixer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjE,OAAO,KAAK,EAAE,YAAY,EAAc,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEjF,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAqErE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAuD/D;AAgOD,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,sBAAsB,GAAG,WAAW,CAAC,CAAC,EAC1E,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,SAAS,CAAC,CA6GpB"}
1
+ {"version":3,"file":"audioMixer.d.ts","sourceRoot":"","sources":["../../src/services/audioMixer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjE,OAAO,KAAK,EAAE,YAAY,EAAc,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGjF,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAyJrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAuD/D;AAmPD,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,sBAAsB,GAAG,WAAW,CAAC,CAAC,EAC1E,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,SAAS,CAAC,CA2HpB"}
@@ -12,6 +12,7 @@ import { DEFAULT_CONFIG } from "../config.js";
12
12
  import { runFfmpeg } from "../utils/runFfmpeg.js";
13
13
  import { unwrapTemplate } from "../utils/htmlTemplate.js";
14
14
  import { resolveProjectRelativeSrc } from "./videoFrameExtractor.js";
15
+ import { applyVolumeEnvelopeToWav } from "./audioVolumeEnvelope.js";
15
16
  function clampVolume(volume) {
16
17
  if (!Number.isFinite(volume))
17
18
  return 1;
@@ -23,10 +24,83 @@ function formatFilterNumber(value) {
23
24
  function escapeExpressionCommas(expression) {
24
25
  return expression.replace(/\\/g, "\\\\").replace(/,/g, "\\,");
25
26
  }
26
- function buildVolumeExpression(track) {
27
+ /**
28
+ * Upper bound on volume-automation keyframes folded into the FFmpeg `volume`
29
+ * expression. The expression nests one `if(lt(...))` per keyframe, and
30
+ * FFmpeg's expression evaluator has a finite nesting depth: past ~95 levels
31
+ * (build-dependent — lower on some Linux ffmpeg builds) `volume=...:eval=frame`
32
+ * fails filter-graph init, which fails the whole mix and drops the audio track
33
+ * entirely. The 60 Hz timeline probe routinely emits 100–300 keyframes for a
34
+ * multi-second fade (GH #1066 follow-up: a 171-keyframe GSAP fade rendered with
35
+ * no audio). 32 segments keeps a wide safety margin and is far more resolution
36
+ * than a piecewise-linear volume envelope needs.
37
+ */
38
+ const MAX_VOLUME_SEGMENTS = 32;
39
+ /**
40
+ * Volume delta below which a keyframe is collinear enough to drop. Kept tight
41
+ * (0.5% linear) so the rendered piecewise-linear envelope tracks the GSAP curve
42
+ * the browser plays in preview to within ~0.2 dB across the audible range — well
43
+ * under the ~1 dB loudness JND, so render stays WYSIWYG with preview. A full
44
+ * ease-in/ease-out fade still reduces to ~25 segments, inside MAX_VOLUME_SEGMENTS.
45
+ */
46
+ const VOLUME_SIMPLIFY_EPSILON = 0.005;
47
+ /**
48
+ * Reduce a sorted keyframe list to a perceptually-equivalent piecewise-linear
49
+ * envelope with a bounded segment count.
50
+ *
51
+ * Ramer–Douglas–Peucker drops control points lying within
52
+ * `VOLUME_SIMPLIFY_EPSILON` of the line through their neighbours (a linear fade
53
+ * collapses to its two endpoints; an eased fade to a handful). A uniform
54
+ * downsample backstop then bounds pathological inputs (e.g. audio-rate volume
55
+ * oscillation) to `MAX_VOLUME_SEGMENTS`. Endpoints are always preserved so the
56
+ * envelope still spans the full clip.
57
+ */
58
+ function simplifyVolumeKeyframes(keyframes) {
59
+ if (keyframes.length < 3)
60
+ return keyframes;
61
+ const keep = new Array(keyframes.length).fill(false);
62
+ keep[0] = true;
63
+ keep[keyframes.length - 1] = true;
64
+ const stack = [[0, keyframes.length - 1]];
65
+ while (stack.length > 0) {
66
+ const [startIndex, endIndex] = stack.pop();
67
+ const start = keyframes[startIndex];
68
+ const end = keyframes[endIndex];
69
+ const span = end.time - start.time;
70
+ let maxDistance = VOLUME_SIMPLIFY_EPSILON;
71
+ let splitIndex = -1;
72
+ for (let i = startIndex + 1; i < endIndex; i += 1) {
73
+ const point = keyframes[i];
74
+ const interpolated = span === 0
75
+ ? start.volume
76
+ : start.volume + ((end.volume - start.volume) * (point.time - start.time)) / span;
77
+ const distance = Math.abs(point.volume - interpolated);
78
+ if (distance > maxDistance) {
79
+ maxDistance = distance;
80
+ splitIndex = i;
81
+ }
82
+ }
83
+ if (splitIndex !== -1) {
84
+ keep[splitIndex] = true;
85
+ stack.push([startIndex, splitIndex], [splitIndex, endIndex]);
86
+ }
87
+ }
88
+ const simplified = keyframes.filter((_, i) => keep[i]);
89
+ if (simplified.length <= MAX_VOLUME_SEGMENTS)
90
+ return simplified;
91
+ const step = (simplified.length - 1) / (MAX_VOLUME_SEGMENTS - 1);
92
+ const sampled = [];
93
+ for (let i = 0; i < MAX_VOLUME_SEGMENTS; i += 1) {
94
+ const point = simplified[Math.round(i * step)];
95
+ if (sampled.length === 0 || point.time > sampled.at(-1).time)
96
+ sampled.push(point);
97
+ }
98
+ return sampled;
99
+ }
100
+ function buildVolumeExpression(track, ignoreKeyframes = false) {
27
101
  const trimDuration = track.end - track.start;
28
102
  const staticVolume = clampVolume(track.volume);
29
- const keyframes = (track.volumeKeyframes ?? [])
103
+ const keyframes = (ignoreKeyframes ? [] : (track.volumeKeyframes ?? []))
30
104
  .filter((keyframe) => Number.isFinite(keyframe.time) && Number.isFinite(keyframe.volume))
31
105
  .map((keyframe) => ({
32
106
  time: Math.max(0, Math.min(trimDuration, keyframe.time - track.start)),
@@ -48,13 +122,17 @@ function buildVolumeExpression(track) {
48
122
  deduped.push(keyframe);
49
123
  }
50
124
  }
51
- if (deduped.length === 1) {
52
- return `volume=${formatFilterNumber(deduped[0].volume)}`;
125
+ // Collapse the densely-sampled probe output to a bounded piecewise-linear
126
+ // envelope. Without this, the nested-if expression below grows one level per
127
+ // keyframe and overflows FFmpeg's expression evaluator (see MAX_VOLUME_SEGMENTS).
128
+ const simplified = simplifyVolumeKeyframes(deduped);
129
+ if (simplified.length === 1) {
130
+ return `volume=${formatFilterNumber(simplified[0].volume)}`;
53
131
  }
54
- let expression = formatFilterNumber(deduped.at(-1).volume);
55
- for (let i = deduped.length - 2; i >= 0; i -= 1) {
56
- const current = deduped[i];
57
- const next = deduped[i + 1];
132
+ let expression = formatFilterNumber(simplified.at(-1).volume);
133
+ for (let i = simplified.length - 2; i >= 0; i -= 1) {
134
+ const current = simplified[i];
135
+ const next = simplified[i + 1];
58
136
  const currentTime = formatFilterNumber(current.time);
59
137
  const nextTime = formatFilterNumber(next.time);
60
138
  const currentVolume = formatFilterNumber(current.volume);
@@ -240,36 +318,52 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
240
318
  const outputDir = dirname(outputPath);
241
319
  if (!existsSync(outputDir))
242
320
  mkdirSync(outputDir, { recursive: true });
243
- const inputs = [];
244
- const filterParts = [];
245
- tracks.forEach((track, i) => {
246
- inputs.push("-i", track.srcPath);
247
- const delayMs = Math.round(track.start * 1000);
248
- const trimDuration = track.end - track.start;
249
- const volumeFilter = buildVolumeExpression(track);
250
- filterParts.push(`[${i}:a]atrim=0:${trimDuration},${volumeFilter},adelay=${delayMs}|${delayMs},apad=whole_dur=${totalDuration}[a${i}]`);
251
- });
252
- const mixInputs = tracks.map((_, i) => `[a${i}]`).join("");
253
- const weights = tracks.map(() => "1").join(" ");
254
- const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0:normalize=0:weights='${weights}'[mixed]`;
255
- const postMixGainFilter = `[mixed]volume=${masterOutputGain}[out]`;
256
- const fullFilter = [...filterParts, mixFilter, postMixGainFilter].join(";");
257
- const args = [
258
- ...inputs,
259
- "-filter_complex",
260
- fullFilter,
261
- "-map",
262
- "[out]",
263
- "-acodec",
264
- "aac",
265
- "-b:a",
266
- "192k",
267
- "-t",
268
- String(totalDuration),
269
- "-y",
270
- outputPath,
271
- ];
272
- const result = await runFfmpeg(args, { signal, timeout: ffmpegProcessTimeout });
321
+ const buildArgs = (ignoreAutomation) => {
322
+ const inputs = [];
323
+ const filterParts = [];
324
+ tracks.forEach((track, i) => {
325
+ inputs.push("-i", track.srcPath);
326
+ const delayMs = Math.round(track.start * 1000);
327
+ const trimDuration = track.end - track.start;
328
+ const volumeFilter = buildVolumeExpression(track, ignoreAutomation);
329
+ filterParts.push(`[${i}:a]atrim=0:${trimDuration},${volumeFilter},adelay=${delayMs}|${delayMs},apad=whole_dur=${totalDuration}[a${i}]`);
330
+ });
331
+ const mixInputs = tracks.map((_, i) => `[a${i}]`).join("");
332
+ const weights = tracks.map(() => "1").join(" ");
333
+ const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0:normalize=0:weights='${weights}'[mixed]`;
334
+ const postMixGainFilter = `[mixed]volume=${masterOutputGain}[out]`;
335
+ const fullFilter = [...filterParts, mixFilter, postMixGainFilter].join(";");
336
+ return [
337
+ ...inputs,
338
+ "-filter_complex",
339
+ fullFilter,
340
+ "-map",
341
+ "[out]",
342
+ "-acodec",
343
+ "aac",
344
+ "-b:a",
345
+ "192k",
346
+ "-t",
347
+ String(totalDuration),
348
+ "-y",
349
+ outputPath,
350
+ ];
351
+ };
352
+ let result = await runFfmpeg(buildArgs(false), { signal, timeout: ffmpegProcessTimeout });
353
+ // Defense in depth: volume automation is folded into an FFmpeg `volume`
354
+ // expression whose evaluator limits are build-dependent (see
355
+ // MAX_VOLUME_SEGMENTS). If that ever fails the mix, retry once without the
356
+ // automation so the track renders at its base volume rather than being
357
+ // dropped from the output entirely — a missing fade beats missing audio.
358
+ let degradedAutomation = false;
359
+ const hasAutomation = tracks.some((track) => (track.volumeKeyframes?.length ?? 0) > 0);
360
+ if (!result.success && !signal?.aborted && hasAutomation) {
361
+ const retry = await runFfmpeg(buildArgs(true), { signal, timeout: ffmpegProcessTimeout });
362
+ if (retry.success) {
363
+ result = retry;
364
+ degradedAutomation = true;
365
+ }
366
+ }
273
367
  if (signal?.aborted) {
274
368
  return {
275
369
  success: false,
@@ -293,6 +387,9 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
293
387
  outputPath,
294
388
  durationMs: result.durationMs,
295
389
  tracksProcessed: tracks.length,
390
+ error: degradedAutomation
391
+ ? "Volume automation exceeded this ffmpeg build's expression limits; rendered at base volume"
392
+ : undefined,
296
393
  };
297
394
  }
298
395
  export async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config, compiledDir) {
@@ -355,6 +452,14 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
355
452
  }
356
453
  audioSrcPath = trimmedPath;
357
454
  }
455
+ // Primary volume-automation path: bake the envelope into the PCM samples
456
+ // (sample-accurate, no keyframe ceiling). If the WAV isn't the expected
457
+ // 16-bit PCM, fall back to the ffmpeg expression path by leaving the
458
+ // keyframes on the track for buildVolumeExpression to handle.
459
+ let bakedEnvelope = false;
460
+ if (element.volumeKeyframes && element.volumeKeyframes.length > 0) {
461
+ bakedEnvelope = applyVolumeEnvelopeToWav(audioSrcPath, element.volumeKeyframes, element.start, element.volume ?? 1.0);
462
+ }
358
463
  tracks.push({
359
464
  id: element.id,
360
465
  srcPath: audioSrcPath,
@@ -362,8 +467,9 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
362
467
  end: element.end,
363
468
  mediaStart: element.mediaStart,
364
469
  duration: element.end - element.start,
365
- volume: element.volume ?? 1.0,
366
- volumeKeyframes: element.volumeKeyframes,
470
+ // Gain is already in the samples when baked, so mix at unity.
471
+ volume: bakedEnvelope ? 1.0 : (element.volume ?? 1.0),
472
+ volumeKeyframes: bakedEnvelope ? undefined : element.volumeKeyframes,
367
473
  });
368
474
  }
369
475
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"audioMixer.js","sourceRoot":"","sources":["../../src/services/audioMixer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAqB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAKrE,SAAS,WAAW,CAAC,MAAc;IACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,sBAAsB,CAAC,UAAkB;IAChD,OAAO,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAiB;IAC9C,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC;IAC7C,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC;SAC5C,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;SACxF,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClB,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC;KACrC,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,UAAU,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;IAEhF,IAAI,SAAS,CAAC,CAAC,CAAE,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACnE,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,UAAU,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC;IAC5D,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,kBAAkB,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,GAAG,aAAa,KAAK,KAAK,QAAQ,WAAW,GAAG,CAAC;QACjE,UAAU,GAAG,WAAW,QAAQ,KAAK,OAAO,IAAI,UAAU,GAAG,CAAC;IAChE,CAAC;IAED,OAAO,UAAU,sBAAsB,CAAC,UAAU,CAAC,aAAa,CAAC;AACnE,CAAC;AASD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IAErD,yBAAyB;IACzB,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAC7D,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG;YAAE,SAAS;QAE1B,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;QAC3D,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAElD,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE;YACF,GAAG;YACH,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACtC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3D,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG;YACjD,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;IACL,CAAC;IAED,oDAAoD;IACpD,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;IACpF,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG;YAAE,SAAS;QAE1B,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;QAC3D,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAElD,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,GAAG,EAAE,QAAQ;YACjB,GAAG;YACH,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACtC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3D,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG;YACjD,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,SAAiB,EACjB,UAAkB,EAClB,OAAmD,EACnD,MAAoB,EACpB,MAA4D;IAE5D,MAAM,oBAAoB,GAAG,MAAM,EAAE,oBAAoB,IAAI,cAAc,CAAC,oBAAoB,CAAC;IACjG,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,IAAI,GAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAClF,IAAI,OAAO,EAAE,QAAQ,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/E,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAEvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAEhF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK,EAAE,yBAAyB;SACjC,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK,EACH,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,2BAA2B,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;SAC1F,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;AACtE,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,OAAe,EACf,UAAkB,EAClB,UAAkB,EAClB,QAAgB,EAChB,MAAoB,EACpB,MAA4D;IAE5D,MAAM,oBAAoB,GAAG,MAAM,EAAE,oBAAoB,IAAI,cAAc,CAAC,oBAAoB,CAAC;IACjG,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,IAAI,GAAG;QACX,KAAK;QACL,MAAM,CAAC,UAAU,CAAC;QAClB,IAAI;QACJ,MAAM,CAAC,QAAQ,CAAC;QAChB,IAAI;QACJ,OAAO;QACP,SAAS;QACT,WAAW;QACX,KAAK;QACL,OAAO;QACP,KAAK;QACL,GAAG;QACH,IAAI;QACJ,UAAU;KACX,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAEhF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK,EAAE,yBAAyB;SACjC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,CAAC,MAAM,CAAC,OAAO;YACpB,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI;gBACxB,CAAC,CAAC,2BAA2B,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE;gBAC5E,CAAC,CAAC,MAAM,CAAC,MAAM;YACjB,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,UAAkB,EAClB,QAAgB,EAChB,MAAoB,EACpB,MAA4D;IAE5D,MAAM,oBAAoB,GAAG,MAAM,EAAE,oBAAoB,IAAI,cAAc,CAAC,oBAAoB,CAAC;IACjG,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,OAAO;QACP,IAAI;QACJ,4BAA4B;QAC5B,IAAI;QACJ,MAAM,CAAC,QAAQ,CAAC;QAChB,SAAS;QACT,WAAW;QACX,IAAI;QACJ,UAAU;KACX,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAEhF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK,EAAE,8BAA8B;SACtC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,CAAC,MAAM,CAAC,OAAO;YACpB,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI;gBACxB,CAAC,CAAC,2BAA2B,MAAM,CAAC,QAAQ,EAAE;gBAC9C,CAAC,CAAC,MAAM,CAAC,MAAM;YACjB,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,MAAoB,EACpB,UAAkB,EAClB,aAAqB,EACrB,MAAoB,EACpB,MAA0E;IAE1E,MAAM,oBAAoB,GAAG,MAAM,EAAE,oBAAoB,IAAI,cAAc,CAAC,oBAAoB,CAAC;IACjG,MAAM,gBAAgB,GAAG,MAAM,EAAE,SAAS,IAAI,cAAc,CAAC,SAAS,CAAC;IAEvE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAChF,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,eAAe,EAAE,CAAC;YAClB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC;QAC7C,MAAM,YAAY,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAClD,WAAW,CAAC,IAAI,CACd,IAAI,CAAC,cAAc,YAAY,IAAI,YAAY,WAAW,OAAO,IAAI,OAAO,mBAAmB,aAAa,KAAK,CAAC,GAAG,CACtH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,GAAG,SAAS,eAAe,MAAM,CAAC,MAAM,+DAA+D,OAAO,UAAU,CAAC;IAC3I,MAAM,iBAAiB,GAAG,iBAAiB,gBAAgB,OAAO,CAAC;IACnE,MAAM,UAAU,GAAG,CAAC,GAAG,WAAW,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE5E,MAAM,IAAI,GAAG;QACX,GAAG,MAAM;QACT,iBAAiB;QACjB,UAAU;QACV,MAAM;QACN,OAAO;QACP,SAAS;QACT,KAAK;QACL,MAAM;QACN,MAAM;QACN,IAAI;QACJ,MAAM,CAAC,aAAa,CAAC;QACrB,IAAI;QACJ,UAAU;KACX,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAEhF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,eAAe,EAAE,CAAC;YAClB,KAAK,EAAE,qBAAqB;SAC7B,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,eAAe,EAAE,CAAC;YAClB,KAAK,EACH,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,2BAA2B,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;SAC1F,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,IAAI;QACb,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,eAAe,EAAE,MAAM,CAAC,MAAM;KAC/B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAwB,EACxB,OAAe,EACf,OAAe,EACf,UAAkB,EAClB,aAAqB,EACrB,MAAoB,EACpB,MAA0E,EAC1E,WAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC;YAC1B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChD,4DAA4D;gBAC5D,qEAAqE;gBACrE,OAAO,GAAG,yBAAyB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;YACzE,CAAC;YAED,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACnD,CAAC;gBAAC,OAAO,GAAY,EAAE,CAAC;oBACtB,MAAM,CAAC,IAAI,CACT,oBAAoB,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACvF,CAAC;oBACF,OAAO;gBACT,CAAC;YACH,CAAC;YAED,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC,qBAAqB,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;YAED,gEAAgE;YAChE,IAAI,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;gBACrC,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;gBACrD,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;gBACxE,OAAO,CAAC,GAAG;oBACT,OAAO,CAAC,KAAK,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;YAC3F,CAAC;YAED,IAAI,YAAY,GAAG,OAAO,CAAC;YAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC;gBACnE,MAAM,aAAa,GAAG,MAAM,qBAAqB,CAC/C,OAAO,EACP,aAAa,EACb;oBACE,SAAS,EAAE,OAAO,CAAC,UAAU;oBAC7B,QAAQ,EAAE,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK;iBACtC,EACD,MAAM,EACN,MAAM,CACP,CAAC;gBACF,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC7C,OAAO;gBACT,CAAC;gBACD,YAAY,GAAG,aAAa,CAAC;YAC/B,CAAC;iBAAM,CAAC;gBACN,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC;gBAC/D,MAAM,UAAU,GAAG,MAAM,iBAAiB,CACxC,OAAO,EACP,WAAW,EACX,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,EAC3B,MAAM,EACN,MAAM,CACP,CAAC;gBACF,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;oBACxB,MAAM,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC7C,OAAO;gBACT,CAAC;gBACD,YAAY,GAAG,WAAW,CAAC;YAC7B,CAAC;YAED,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,OAAO,EAAE,YAAY;gBACrB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,QAAQ,EAAE,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK;gBACrC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG;gBAC7B,eAAe,EAAE,OAAO,CAAC,eAAe;aACzC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAE1F,IAAI,CAAC;QACH,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,GAAG,SAAS;QACZ,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;QAChC,KAAK,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK;KAC9E,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"audioMixer.js","sourceRoot":"","sources":["../../src/services/audioMixer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Sample-accurate volume automation.
3
+ *
4
+ * The audio mixer's primary path for time-varying volume bakes the envelope
5
+ * directly into the prepared PCM rather than encoding it as an FFmpeg `volume`
6
+ * expression. The expression approach nests one `if(lt(t,...))` per keyframe and
7
+ * overflows FFmpeg's expression evaluator past ~95 levels (a dense GSAP fade
8
+ * emits hundreds of keyframes), which fails the whole mix and drops the audio
9
+ * track. Multiplying the samples in-house has no such ceiling, is exact at every
10
+ * sample, and keeps the downstream ffmpeg `amix`/AAC encode untouched — so the
11
+ * output (and the golden baselines) only change where a fade is actually applied.
12
+ *
13
+ * The prepared tracks are always `pcm_s16le`, 48 kHz, stereo (see
14
+ * `prepareAudioTrack` / `extractAudioFromVideo`). Anything else is rejected so
15
+ * the caller can fall back to the expression path rather than corrupting audio.
16
+ */
17
+ import type { AudioVolumeKeyframe } from "./audioMixer.types.js";
18
+ /**
19
+ * Multiply a prepared WAV's samples by a time-varying gain envelope in place.
20
+ *
21
+ * @returns `true` if the envelope was applied; `false` if the file isn't the
22
+ * expected 16-bit PCM (caller should fall back to the expression path).
23
+ */
24
+ export declare function applyVolumeEnvelopeToWav(wavPath: string, keyframes: AudioVolumeKeyframe[], trackStart: number, baseVolume: number): boolean;
25
+ //# sourceMappingURL=audioVolumeEnvelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioVolumeEnvelope.d.ts","sourceRoot":"","sources":["../../src/services/audioVolumeEnvelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAyDjE;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,mBAAmB,EAAE,EAChC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAkDT"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Sample-accurate volume automation.
3
+ *
4
+ * The audio mixer's primary path for time-varying volume bakes the envelope
5
+ * directly into the prepared PCM rather than encoding it as an FFmpeg `volume`
6
+ * expression. The expression approach nests one `if(lt(t,...))` per keyframe and
7
+ * overflows FFmpeg's expression evaluator past ~95 levels (a dense GSAP fade
8
+ * emits hundreds of keyframes), which fails the whole mix and drops the audio
9
+ * track. Multiplying the samples in-house has no such ceiling, is exact at every
10
+ * sample, and keeps the downstream ffmpeg `amix`/AAC encode untouched — so the
11
+ * output (and the golden baselines) only change where a fade is actually applied.
12
+ *
13
+ * The prepared tracks are always `pcm_s16le`, 48 kHz, stereo (see
14
+ * `prepareAudioTrack` / `extractAudioFromVideo`). Anything else is rejected so
15
+ * the caller can fall back to the expression path rather than corrupting audio.
16
+ */
17
+ import { readFileSync, renameSync, writeFileSync } from "fs";
18
+ import { randomBytes } from "crypto";
19
+ import { normaliseEnvelope } from "@hyperframes/core/media-volume-envelope";
20
+ const PCM_FORMAT = 1; // WAVE_FORMAT_PCM
21
+ const SUPPORTED_BITS = 16;
22
+ /**
23
+ * Locate the `fmt ` and `data` chunks and validate the format we know how to edit.
24
+ *
25
+ * Scans every chunk rather than assuming an ordering: the loop always advances
26
+ * past a chunk's body (using its declared size), so `data` may precede `fmt `
27
+ * and trailing chunks (LIST/fact/etc.) are skipped harmlessly. Returns null on
28
+ * anything unexpected so the caller falls back to the expression path.
29
+ */
30
+ function parseWavLayout(buffer) {
31
+ if (buffer.length < 12 || buffer.toString("ascii", 0, 4) !== "RIFF")
32
+ return null;
33
+ if (buffer.toString("ascii", 8, 12) !== "WAVE")
34
+ return null;
35
+ let offset = 12;
36
+ let fmt = null;
37
+ let data = null;
38
+ while (offset + 8 <= buffer.length) {
39
+ const chunkId = buffer.toString("ascii", offset, offset + 4);
40
+ const chunkSize = buffer.readUInt32LE(offset + 4);
41
+ const body = offset + 8;
42
+ if (chunkId === "fmt " && body + 16 <= buffer.length) {
43
+ if (buffer.readUInt16LE(body) !== PCM_FORMAT)
44
+ return null;
45
+ fmt = {
46
+ numChannels: buffer.readUInt16LE(body + 2),
47
+ sampleRate: buffer.readUInt32LE(body + 4),
48
+ bitsPerSample: buffer.readUInt16LE(body + 14),
49
+ };
50
+ }
51
+ else if (chunkId === "data") {
52
+ data = { offset: body, size: Math.min(chunkSize, buffer.length - body) };
53
+ }
54
+ // Chunks are word-aligned: an odd size carries a trailing pad byte.
55
+ offset = body + chunkSize + (chunkSize % 2);
56
+ }
57
+ if (!fmt || !data)
58
+ return null;
59
+ if (fmt.bitsPerSample !== SUPPORTED_BITS || fmt.numChannels < 1)
60
+ return null;
61
+ return {
62
+ numChannels: fmt.numChannels,
63
+ sampleRate: fmt.sampleRate,
64
+ dataOffset: data.offset,
65
+ dataSize: data.size,
66
+ };
67
+ }
68
+ /**
69
+ * Multiply a prepared WAV's samples by a time-varying gain envelope in place.
70
+ *
71
+ * @returns `true` if the envelope was applied; `false` if the file isn't the
72
+ * expected 16-bit PCM (caller should fall back to the expression path).
73
+ */
74
+ export function applyVolumeEnvelopeToWav(wavPath, keyframes, trackStart, baseVolume) {
75
+ const envelope = normaliseEnvelope(keyframes, trackStart, baseVolume);
76
+ if (envelope.length === 0)
77
+ return false;
78
+ try {
79
+ const buffer = readFileSync(wavPath);
80
+ const layout = parseWavLayout(buffer);
81
+ if (!layout)
82
+ return false;
83
+ const { numChannels, sampleRate, dataOffset, dataSize } = layout;
84
+ const bytesPerSample = SUPPORTED_BITS / 8;
85
+ const frameBytes = numChannels * bytesPerSample;
86
+ const frameCount = Math.floor(dataSize / frameBytes);
87
+ // Maintain an incremental segment cursor so the per-frame envelope lookup
88
+ // is O(N+M) overall, not O(N×M). interpolateVolumeGain restarts from 0 on
89
+ // each call — fine for the preview path (one call per RAF tick) but not for
90
+ // the PCM path (one call per sample, 48k×duration frames total).
91
+ let segment = 0;
92
+ for (let frame = 0; frame < frameCount; frame += 1) {
93
+ const time = frame / sampleRate;
94
+ while (segment < envelope.length - 2 && time >= envelope[segment + 1].time)
95
+ segment += 1;
96
+ const a = envelope[segment];
97
+ const b = envelope[segment + 1] ?? a;
98
+ const span = b.time - a.time;
99
+ const progress = span <= 0 ? 0 : Math.min(1, Math.max(0, (time - a.time) / span));
100
+ const gain = a.volume + (b.volume - a.volume) * progress;
101
+ const base = dataOffset + frame * frameBytes;
102
+ for (let channel = 0; channel < numChannels; channel += 1) {
103
+ const at = base + channel * bytesPerSample;
104
+ const scaled = Math.round(buffer.readInt16LE(at) * gain);
105
+ buffer.writeInt16LE(scaled < -32768 ? -32768 : scaled > 32767 ? 32767 : scaled, at);
106
+ }
107
+ }
108
+ // Write to a uniquely-named sibling then atomically rename over the
109
+ // original. The random name avoids following a pre-planted symlink at a
110
+ // predictable path, and the rename means a crash mid-write can't leave a
111
+ // truncated WAV for the downstream mix.
112
+ const tempPath = `${wavPath}.${randomBytes(6).toString("hex")}.tmp`;
113
+ writeFileSync(tempPath, buffer);
114
+ renameSync(tempPath, wavPath);
115
+ return true;
116
+ }
117
+ catch {
118
+ // Any read/parse/write failure → leave the file untouched and let the
119
+ // caller fall back to the ffmpeg expression path rather than losing audio.
120
+ return false;
121
+ }
122
+ }
123
+ //# sourceMappingURL=audioVolumeEnvelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioVolumeEnvelope.js","sourceRoot":"","sources":["../../src/services/audioVolumeEnvelope.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.6.55",
3
+ "version": "0.6.57",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  "linkedom": "^0.18.12",
22
22
  "puppeteer": "^24.0.0",
23
23
  "puppeteer-core": "^24.39.1",
24
- "@hyperframes/core": "^0.6.55"
24
+ "@hyperframes/core": "^0.6.57"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^25.0.10",
@@ -108,6 +108,121 @@ describe("processCompositionAudio", () => {
108
108
  expect(filter).toContain("adelay=2000|2000");
109
109
  });
110
110
 
111
+ it("bounds expression nesting for dense keyframe automation without dropping the envelope", async () => {
112
+ const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
113
+ const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
114
+ tempDirs.push(baseDir, workDir);
115
+
116
+ writeFileSync(join(baseDir, "bgm.wav"), "stub");
117
+
118
+ // Mirrors the 60 Hz timeline probe: a 10s eased fade emits hundreds of
119
+ // keyframes. The nested-if volume expression must not grow one level per
120
+ // keyframe — past ~95 levels FFmpeg fails filter-graph init and the audio
121
+ // track is dropped entirely (GH #1066 follow-up).
122
+ const keyframes = Array.from({ length: 300 }, (_, i) => {
123
+ const time = (i / 299) * 10;
124
+ const volume =
125
+ time < 3 ? 0.8 * (time / 3) ** 2 : time < 7 ? 0.8 : 0.8 * (1 - (time - 7) / 3) ** 2;
126
+ return { time, volume };
127
+ });
128
+
129
+ const result = await processCompositionAudio(
130
+ [
131
+ {
132
+ id: "bgm",
133
+ src: "bgm.wav",
134
+ start: 0,
135
+ end: 10,
136
+ mediaStart: 0,
137
+ layer: 0,
138
+ volume: 0,
139
+ volumeKeyframes: keyframes,
140
+ type: "audio",
141
+ },
142
+ ],
143
+ baseDir,
144
+ workDir,
145
+ join(baseDir, "out.m4a"),
146
+ 10,
147
+ );
148
+
149
+ expect(result.success).toBe(true);
150
+
151
+ const mixArgs = runFfmpegMock.mock.calls[1]?.[0];
152
+ const filterIndex = mixArgs.indexOf("-filter_complex");
153
+ const filter = mixArgs[filterIndex + 1];
154
+
155
+ // One nested `if(lt(...))` is emitted per segment; cap it well under the
156
+ // FFmpeg evaluator's nesting limit (MAX_VOLUME_SEGMENTS = 32).
157
+ const nestingDepth = (filter.match(/if\(lt\(t/g) ?? []).length;
158
+ expect(nestingDepth).toBeGreaterThan(1);
159
+ expect(nestingDepth).toBeLessThan(32);
160
+
161
+ // The simplified envelope still spans the clip: silent start, audible peak.
162
+ expect(filter).toContain(":eval=frame");
163
+ expect(filter).toMatch(/volume=if\(lt\(t\\,[0-9.]+\)\\,0\+/);
164
+ });
165
+
166
+ it("falls back to a static-volume mix instead of dropping audio when the automated mix fails", async () => {
167
+ const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
168
+ const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
169
+ tempDirs.push(baseDir, workDir);
170
+
171
+ writeFileSync(join(baseDir, "bgm.wav"), "stub");
172
+
173
+ // Simulate an ffmpeg build that rejects the automation expression: the
174
+ // first mix attempt fails, the static-volume retry succeeds. (prepare =
175
+ // call 0, automated mix = call 1, fallback mix = call 2.)
176
+ runFfmpegMock
177
+ .mockImplementationOnce(async () => ({
178
+ success: true,
179
+ durationMs: 1,
180
+ stderr: "",
181
+ exitCode: 0,
182
+ }))
183
+ .mockImplementationOnce(async () => ({
184
+ success: false,
185
+ durationMs: 1,
186
+ stderr: "Error initializing filters",
187
+ exitCode: 234,
188
+ }));
189
+
190
+ const result = await processCompositionAudio(
191
+ [
192
+ {
193
+ id: "bgm",
194
+ src: "bgm.wav",
195
+ start: 0,
196
+ end: 5,
197
+ mediaStart: 0,
198
+ layer: 0,
199
+ volume: 0.8,
200
+ volumeKeyframes: [
201
+ { time: 0, volume: 0.8 },
202
+ { time: 5, volume: 0 },
203
+ ],
204
+ type: "audio",
205
+ },
206
+ ],
207
+ baseDir,
208
+ workDir,
209
+ join(baseDir, "out.m4a"),
210
+ 5,
211
+ );
212
+
213
+ expect(result.success).toBe(true);
214
+ expect(result.tracksProcessed).toBe(1);
215
+ expect(runFfmpegMock).toHaveBeenCalledTimes(3);
216
+ // Degradation is surfaced, not silent — the track rendered at base volume.
217
+ expect(result.error).toMatch(/base volume/i);
218
+
219
+ // The fallback mix omits the automation expression (base volume only).
220
+ const fallbackArgs = runFfmpegMock.mock.calls[2]?.[0];
221
+ const fallbackFilter = fallbackArgs[fallbackArgs.indexOf("-filter_complex") + 1];
222
+ expect(fallbackFilter).not.toContain(":eval=frame");
223
+ expect(fallbackFilter).toContain("volume=0.8");
224
+ });
225
+
111
226
  it("prepares percent-encoded non-Latin audio srcs from decoded filesystem paths", async () => {
112
227
  const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
113
228
  const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
@@ -14,6 +14,7 @@ import { runFfmpeg } from "../utils/runFfmpeg.js";
14
14
  import { unwrapTemplate } from "../utils/htmlTemplate.js";
15
15
  import { resolveProjectRelativeSrc } from "./videoFrameExtractor.js";
16
16
  import type { AudioElement, AudioTrack, MixResult } from "./audioMixer.types.js";
17
+ import { applyVolumeEnvelopeToWav } from "./audioVolumeEnvelope.js";
17
18
 
18
19
  export type { AudioElement, MixResult } from "./audioMixer.types.js";
19
20
 
@@ -30,10 +31,89 @@ function escapeExpressionCommas(expression: string): string {
30
31
  return expression.replace(/\\/g, "\\\\").replace(/,/g, "\\,");
31
32
  }
32
33
 
33
- function buildVolumeExpression(track: AudioTrack): string {
34
+ /**
35
+ * Upper bound on volume-automation keyframes folded into the FFmpeg `volume`
36
+ * expression. The expression nests one `if(lt(...))` per keyframe, and
37
+ * FFmpeg's expression evaluator has a finite nesting depth: past ~95 levels
38
+ * (build-dependent — lower on some Linux ffmpeg builds) `volume=...:eval=frame`
39
+ * fails filter-graph init, which fails the whole mix and drops the audio track
40
+ * entirely. The 60 Hz timeline probe routinely emits 100–300 keyframes for a
41
+ * multi-second fade (GH #1066 follow-up: a 171-keyframe GSAP fade rendered with
42
+ * no audio). 32 segments keeps a wide safety margin and is far more resolution
43
+ * than a piecewise-linear volume envelope needs.
44
+ */
45
+ const MAX_VOLUME_SEGMENTS = 32;
46
+
47
+ /**
48
+ * Volume delta below which a keyframe is collinear enough to drop. Kept tight
49
+ * (0.5% linear) so the rendered piecewise-linear envelope tracks the GSAP curve
50
+ * the browser plays in preview to within ~0.2 dB across the audible range — well
51
+ * under the ~1 dB loudness JND, so render stays WYSIWYG with preview. A full
52
+ * ease-in/ease-out fade still reduces to ~25 segments, inside MAX_VOLUME_SEGMENTS.
53
+ */
54
+ const VOLUME_SIMPLIFY_EPSILON = 0.005;
55
+
56
+ /**
57
+ * Reduce a sorted keyframe list to a perceptually-equivalent piecewise-linear
58
+ * envelope with a bounded segment count.
59
+ *
60
+ * Ramer–Douglas–Peucker drops control points lying within
61
+ * `VOLUME_SIMPLIFY_EPSILON` of the line through their neighbours (a linear fade
62
+ * collapses to its two endpoints; an eased fade to a handful). A uniform
63
+ * downsample backstop then bounds pathological inputs (e.g. audio-rate volume
64
+ * oscillation) to `MAX_VOLUME_SEGMENTS`. Endpoints are always preserved so the
65
+ * envelope still spans the full clip.
66
+ */
67
+ function simplifyVolumeKeyframes(
68
+ keyframes: { time: number; volume: number }[],
69
+ ): { time: number; volume: number }[] {
70
+ if (keyframes.length < 3) return keyframes;
71
+
72
+ const keep = new Array<boolean>(keyframes.length).fill(false);
73
+ keep[0] = true;
74
+ keep[keyframes.length - 1] = true;
75
+ const stack: [number, number][] = [[0, keyframes.length - 1]];
76
+ while (stack.length > 0) {
77
+ const [startIndex, endIndex] = stack.pop()!;
78
+ const start = keyframes[startIndex]!;
79
+ const end = keyframes[endIndex]!;
80
+ const span = end.time - start.time;
81
+ let maxDistance = VOLUME_SIMPLIFY_EPSILON;
82
+ let splitIndex = -1;
83
+ for (let i = startIndex + 1; i < endIndex; i += 1) {
84
+ const point = keyframes[i]!;
85
+ const interpolated =
86
+ span === 0
87
+ ? start.volume
88
+ : start.volume + ((end.volume - start.volume) * (point.time - start.time)) / span;
89
+ const distance = Math.abs(point.volume - interpolated);
90
+ if (distance > maxDistance) {
91
+ maxDistance = distance;
92
+ splitIndex = i;
93
+ }
94
+ }
95
+ if (splitIndex !== -1) {
96
+ keep[splitIndex] = true;
97
+ stack.push([startIndex, splitIndex], [splitIndex, endIndex]);
98
+ }
99
+ }
100
+
101
+ const simplified = keyframes.filter((_, i) => keep[i]);
102
+ if (simplified.length <= MAX_VOLUME_SEGMENTS) return simplified;
103
+
104
+ const step = (simplified.length - 1) / (MAX_VOLUME_SEGMENTS - 1);
105
+ const sampled: { time: number; volume: number }[] = [];
106
+ for (let i = 0; i < MAX_VOLUME_SEGMENTS; i += 1) {
107
+ const point = simplified[Math.round(i * step)]!;
108
+ if (sampled.length === 0 || point.time > sampled.at(-1)!.time) sampled.push(point);
109
+ }
110
+ return sampled;
111
+ }
112
+
113
+ function buildVolumeExpression(track: AudioTrack, ignoreKeyframes = false): string {
34
114
  const trimDuration = track.end - track.start;
35
115
  const staticVolume = clampVolume(track.volume);
36
- const keyframes = (track.volumeKeyframes ?? [])
116
+ const keyframes = (ignoreKeyframes ? [] : (track.volumeKeyframes ?? []))
37
117
  .filter((keyframe) => Number.isFinite(keyframe.time) && Number.isFinite(keyframe.volume))
38
118
  .map((keyframe) => ({
39
119
  time: Math.max(0, Math.min(trimDuration, keyframe.time - track.start)),
@@ -57,14 +137,19 @@ function buildVolumeExpression(track: AudioTrack): string {
57
137
  }
58
138
  }
59
139
 
60
- if (deduped.length === 1) {
61
- return `volume=${formatFilterNumber(deduped[0]!.volume)}`;
140
+ // Collapse the densely-sampled probe output to a bounded piecewise-linear
141
+ // envelope. Without this, the nested-if expression below grows one level per
142
+ // keyframe and overflows FFmpeg's expression evaluator (see MAX_VOLUME_SEGMENTS).
143
+ const simplified = simplifyVolumeKeyframes(deduped);
144
+
145
+ if (simplified.length === 1) {
146
+ return `volume=${formatFilterNumber(simplified[0]!.volume)}`;
62
147
  }
63
148
 
64
- let expression = formatFilterNumber(deduped.at(-1)!.volume);
65
- for (let i = deduped.length - 2; i >= 0; i -= 1) {
66
- const current = deduped[i]!;
67
- const next = deduped[i + 1]!;
149
+ let expression = formatFilterNumber(simplified.at(-1)!.volume);
150
+ for (let i = simplified.length - 2; i >= 0; i -= 1) {
151
+ const current = simplified[i]!;
152
+ const next = simplified[i + 1]!;
68
153
  const currentTime = formatFilterNumber(current.time);
69
154
  const nextTime = formatFilterNumber(next.time);
70
155
  const currentVolume = formatFilterNumber(current.volume);
@@ -299,42 +384,58 @@ async function mixAudioTracks(
299
384
  const outputDir = dirname(outputPath);
300
385
  if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
301
386
 
302
- const inputs: string[] = [];
303
- const filterParts: string[] = [];
304
-
305
- tracks.forEach((track, i) => {
306
- inputs.push("-i", track.srcPath);
307
- const delayMs = Math.round(track.start * 1000);
308
- const trimDuration = track.end - track.start;
309
- const volumeFilter = buildVolumeExpression(track);
310
- filterParts.push(
311
- `[${i}:a]atrim=0:${trimDuration},${volumeFilter},adelay=${delayMs}|${delayMs},apad=whole_dur=${totalDuration}[a${i}]`,
312
- );
313
- });
314
-
315
- const mixInputs = tracks.map((_, i) => `[a${i}]`).join("");
316
- const weights = tracks.map(() => "1").join(" ");
317
- const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0:normalize=0:weights='${weights}'[mixed]`;
318
- const postMixGainFilter = `[mixed]volume=${masterOutputGain}[out]`;
319
- const fullFilter = [...filterParts, mixFilter, postMixGainFilter].join(";");
387
+ const buildArgs = (ignoreAutomation: boolean): string[] => {
388
+ const inputs: string[] = [];
389
+ const filterParts: string[] = [];
390
+ tracks.forEach((track, i) => {
391
+ inputs.push("-i", track.srcPath);
392
+ const delayMs = Math.round(track.start * 1000);
393
+ const trimDuration = track.end - track.start;
394
+ const volumeFilter = buildVolumeExpression(track, ignoreAutomation);
395
+ filterParts.push(
396
+ `[${i}:a]atrim=0:${trimDuration},${volumeFilter},adelay=${delayMs}|${delayMs},apad=whole_dur=${totalDuration}[a${i}]`,
397
+ );
398
+ });
320
399
 
321
- const args = [
322
- ...inputs,
323
- "-filter_complex",
324
- fullFilter,
325
- "-map",
326
- "[out]",
327
- "-acodec",
328
- "aac",
329
- "-b:a",
330
- "192k",
331
- "-t",
332
- String(totalDuration),
333
- "-y",
334
- outputPath,
335
- ];
400
+ const mixInputs = tracks.map((_, i) => `[a${i}]`).join("");
401
+ const weights = tracks.map(() => "1").join(" ");
402
+ const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0:normalize=0:weights='${weights}'[mixed]`;
403
+ const postMixGainFilter = `[mixed]volume=${masterOutputGain}[out]`;
404
+ const fullFilter = [...filterParts, mixFilter, postMixGainFilter].join(";");
405
+
406
+ return [
407
+ ...inputs,
408
+ "-filter_complex",
409
+ fullFilter,
410
+ "-map",
411
+ "[out]",
412
+ "-acodec",
413
+ "aac",
414
+ "-b:a",
415
+ "192k",
416
+ "-t",
417
+ String(totalDuration),
418
+ "-y",
419
+ outputPath,
420
+ ];
421
+ };
336
422
 
337
- const result = await runFfmpeg(args, { signal, timeout: ffmpegProcessTimeout });
423
+ let result = await runFfmpeg(buildArgs(false), { signal, timeout: ffmpegProcessTimeout });
424
+
425
+ // Defense in depth: volume automation is folded into an FFmpeg `volume`
426
+ // expression whose evaluator limits are build-dependent (see
427
+ // MAX_VOLUME_SEGMENTS). If that ever fails the mix, retry once without the
428
+ // automation so the track renders at its base volume rather than being
429
+ // dropped from the output entirely — a missing fade beats missing audio.
430
+ let degradedAutomation = false;
431
+ const hasAutomation = tracks.some((track) => (track.volumeKeyframes?.length ?? 0) > 0);
432
+ if (!result.success && !signal?.aborted && hasAutomation) {
433
+ const retry = await runFfmpeg(buildArgs(true), { signal, timeout: ffmpegProcessTimeout });
434
+ if (retry.success) {
435
+ result = retry;
436
+ degradedAutomation = true;
437
+ }
438
+ }
338
439
 
339
440
  if (signal?.aborted) {
340
441
  return {
@@ -360,6 +461,9 @@ async function mixAudioTracks(
360
461
  outputPath,
361
462
  durationMs: result.durationMs,
362
463
  tracksProcessed: tracks.length,
464
+ error: degradedAutomation
465
+ ? "Volume automation exceeded this ffmpeg build's expression limits; rendered at base volume"
466
+ : undefined,
363
467
  };
364
468
  }
365
469
 
@@ -452,6 +556,19 @@ export async function processCompositionAudio(
452
556
  audioSrcPath = trimmedPath;
453
557
  }
454
558
 
559
+ // Primary volume-automation path: bake the envelope into the PCM samples
560
+ // (sample-accurate, no keyframe ceiling). If the WAV isn't the expected
561
+ // 16-bit PCM, fall back to the ffmpeg expression path by leaving the
562
+ // keyframes on the track for buildVolumeExpression to handle.
563
+ let bakedEnvelope = false;
564
+ if (element.volumeKeyframes && element.volumeKeyframes.length > 0) {
565
+ bakedEnvelope = applyVolumeEnvelopeToWav(
566
+ audioSrcPath,
567
+ element.volumeKeyframes,
568
+ element.start,
569
+ element.volume ?? 1.0,
570
+ );
571
+ }
455
572
  tracks.push({
456
573
  id: element.id,
457
574
  srcPath: audioSrcPath,
@@ -459,8 +576,9 @@ export async function processCompositionAudio(
459
576
  end: element.end,
460
577
  mediaStart: element.mediaStart,
461
578
  duration: element.end - element.start,
462
- volume: element.volume ?? 1.0,
463
- volumeKeyframes: element.volumeKeyframes,
579
+ // Gain is already in the samples when baked, so mix at unity.
580
+ volume: bakedEnvelope ? 1.0 : (element.volume ?? 1.0),
581
+ volumeKeyframes: bakedEnvelope ? undefined : element.volumeKeyframes,
464
582
  });
465
583
  } catch (err: unknown) {
466
584
  errors.push(`Error: ${element.id} — ${err instanceof Error ? err.message : String(err)}`);
@@ -0,0 +1,176 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { applyVolumeEnvelopeToWav } from "./audioVolumeEnvelope.js";
6
+
7
+ const SAMPLE_RATE = 48000;
8
+ const CHANNELS = 2;
9
+
10
+ /** Build a PCM s16le stereo WAV whose every sample equals `value`. */
11
+ function writeConstantWav(path: string, frames: number, value: number): void {
12
+ const bytesPerSample = 2;
13
+ const dataSize = frames * CHANNELS * bytesPerSample;
14
+ const buffer = Buffer.alloc(44 + dataSize);
15
+ buffer.write("RIFF", 0, "ascii");
16
+ buffer.writeUInt32LE(36 + dataSize, 4);
17
+ buffer.write("WAVE", 8, "ascii");
18
+ buffer.write("fmt ", 12, "ascii");
19
+ buffer.writeUInt32LE(16, 16);
20
+ buffer.writeUInt16LE(1, 20); // PCM
21
+ buffer.writeUInt16LE(CHANNELS, 22);
22
+ buffer.writeUInt32LE(SAMPLE_RATE, 24);
23
+ buffer.writeUInt32LE(SAMPLE_RATE * CHANNELS * bytesPerSample, 28);
24
+ buffer.writeUInt16LE(CHANNELS * bytesPerSample, 32);
25
+ buffer.writeUInt16LE(16, 34);
26
+ buffer.write("data", 36, "ascii");
27
+ buffer.writeUInt32LE(dataSize, 40);
28
+ for (let i = 0; i < frames * CHANNELS; i += 1) buffer.writeInt16LE(value, 44 + i * 2);
29
+ writeFileSync(path, buffer);
30
+ }
31
+
32
+ function sampleAt(path: string, frame: number, channel = 0): number {
33
+ const buffer = readFileSync(path);
34
+ return buffer.readInt16LE(44 + (frame * CHANNELS + channel) * 2);
35
+ }
36
+
37
+ describe("applyVolumeEnvelopeToWav", () => {
38
+ const dirs: string[] = [];
39
+ const tmp = () => {
40
+ const d = mkdtempSync(join(tmpdir(), "hf-env-"));
41
+ dirs.push(d);
42
+ return d;
43
+ };
44
+ afterEach(() => {
45
+ for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
46
+ });
47
+
48
+ it("applies a linear fade sample-accurately", () => {
49
+ const path = join(tmp(), "a.wav");
50
+ const frames = SAMPLE_RATE; // 1 second
51
+ writeConstantWav(path, frames, 10000);
52
+
53
+ // Fade 0 -> 1 over the full second.
54
+ const applied = applyVolumeEnvelopeToWav(
55
+ path,
56
+ [
57
+ { time: 0, volume: 0 },
58
+ { time: 1, volume: 1 },
59
+ ],
60
+ 0,
61
+ 0,
62
+ );
63
+ expect(applied).toBe(true);
64
+
65
+ expect(sampleAt(path, 0)).toBe(0); // gain 0
66
+ expect(sampleAt(path, frames / 2)).toBeCloseTo(5000, -2); // gain ~0.5
67
+ expect(sampleAt(path, frames - 1)).toBeGreaterThan(9900); // gain ~1
68
+ });
69
+
70
+ it("offsets keyframes by the track start (composition time -> track-relative)", () => {
71
+ const path = join(tmp(), "b.wav");
72
+ const frames = SAMPLE_RATE;
73
+ writeConstantWav(path, frames, 10000);
74
+
75
+ // Track starts at 5s; the fade runs from comp-time 5s..6s -> wav 0s..1s.
76
+ applyVolumeEnvelopeToWav(
77
+ path,
78
+ [
79
+ { time: 5, volume: 0 },
80
+ { time: 6, volume: 1 },
81
+ ],
82
+ 5,
83
+ 0,
84
+ );
85
+
86
+ expect(sampleAt(path, 0)).toBe(0);
87
+ expect(sampleAt(path, frames / 2)).toBeCloseTo(5000, -2);
88
+ });
89
+
90
+ it("holds base volume before the first keyframe and the last value after", () => {
91
+ const path = join(tmp(), "c.wav");
92
+ const frames = SAMPLE_RATE * 3; // 3 seconds
93
+ writeConstantWav(path, frames, 10000);
94
+
95
+ // Base 0.8 held until a fade-out begins at 2s.
96
+ applyVolumeEnvelopeToWav(
97
+ path,
98
+ [
99
+ { time: 2, volume: 0.8 },
100
+ { time: 3, volume: 0 },
101
+ ],
102
+ 0,
103
+ 0.8,
104
+ );
105
+
106
+ expect(sampleAt(path, SAMPLE_RATE)).toBeCloseTo(8000, -2); // 1s: base 0.8
107
+ expect(sampleAt(path, frames - 1)).toBeLessThan(200); // 3s: faded to ~0
108
+ });
109
+
110
+ it("handles thousands of keyframes without failing (no expression ceiling)", () => {
111
+ const path = join(tmp(), "d.wav");
112
+ const frames = SAMPLE_RATE * 2;
113
+ writeConstantWav(path, frames, 10000);
114
+
115
+ const keyframes = Array.from({ length: 5000 }, (_, i) => ({
116
+ time: (i / 4999) * 2,
117
+ volume: Math.abs(Math.sin(i / 50)),
118
+ }));
119
+ expect(applyVolumeEnvelopeToWav(path, keyframes, 0, 0)).toBe(true);
120
+ });
121
+
122
+ it("parses chunks in any order (data before fmt)", () => {
123
+ const path = join(tmp(), "order.wav");
124
+ const frames = 4;
125
+ const dataSize = frames * CHANNELS * 2;
126
+ // Lay the data chunk before fmt to exercise order-independent scanning.
127
+ const buffer = Buffer.alloc(12 + (8 + dataSize) + (8 + 16));
128
+ buffer.write("RIFF", 0, "ascii");
129
+ buffer.writeUInt32LE(buffer.length - 8, 4);
130
+ buffer.write("WAVE", 8, "ascii");
131
+ let o = 12;
132
+ buffer.write("data", o, "ascii");
133
+ buffer.writeUInt32LE(dataSize, o + 4);
134
+ for (let i = 0; i < frames * CHANNELS; i += 1) buffer.writeInt16LE(10000, o + 8 + i * 2);
135
+ o += 8 + dataSize;
136
+ buffer.write("fmt ", o, "ascii");
137
+ buffer.writeUInt32LE(16, o + 4);
138
+ buffer.writeUInt16LE(1, o + 8);
139
+ buffer.writeUInt16LE(CHANNELS, o + 10);
140
+ buffer.writeUInt32LE(SAMPLE_RATE, o + 12);
141
+ buffer.writeUInt16LE(16, o + 22);
142
+ writeFileSync(path, buffer);
143
+
144
+ expect(applyVolumeEnvelopeToWav(path, [{ time: 0, volume: 0 }], 0, 0)).toBe(true);
145
+ expect(readFileSync(path).readInt16LE(12 + 8)).toBe(0); // first sample muted
146
+ });
147
+
148
+ it("rejects non-16-bit PCM so the caller can fall back", () => {
149
+ const path = join(tmp(), "e.wav");
150
+ // 24-bit PCM header (bitsPerSample = 24); body contents are irrelevant.
151
+ const buffer = Buffer.alloc(44);
152
+ buffer.write("RIFF", 0, "ascii");
153
+ buffer.write("WAVE", 8, "ascii");
154
+ buffer.write("fmt ", 12, "ascii");
155
+ buffer.writeUInt32LE(16, 16);
156
+ buffer.writeUInt16LE(1, 20);
157
+ buffer.writeUInt16LE(CHANNELS, 22);
158
+ buffer.writeUInt32LE(SAMPLE_RATE, 24);
159
+ buffer.writeUInt16LE(24, 34);
160
+ buffer.write("data", 36, "ascii");
161
+ buffer.writeUInt32LE(0, 40);
162
+ writeFileSync(path, buffer);
163
+
164
+ expect(
165
+ applyVolumeEnvelopeToWav(
166
+ path,
167
+ [
168
+ { time: 0, volume: 0 },
169
+ { time: 1, volume: 1 },
170
+ ],
171
+ 0,
172
+ 0,
173
+ ),
174
+ ).toBe(false);
175
+ });
176
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Sample-accurate volume automation.
3
+ *
4
+ * The audio mixer's primary path for time-varying volume bakes the envelope
5
+ * directly into the prepared PCM rather than encoding it as an FFmpeg `volume`
6
+ * expression. The expression approach nests one `if(lt(t,...))` per keyframe and
7
+ * overflows FFmpeg's expression evaluator past ~95 levels (a dense GSAP fade
8
+ * emits hundreds of keyframes), which fails the whole mix and drops the audio
9
+ * track. Multiplying the samples in-house has no such ceiling, is exact at every
10
+ * sample, and keeps the downstream ffmpeg `amix`/AAC encode untouched — so the
11
+ * output (and the golden baselines) only change where a fade is actually applied.
12
+ *
13
+ * The prepared tracks are always `pcm_s16le`, 48 kHz, stereo (see
14
+ * `prepareAudioTrack` / `extractAudioFromVideo`). Anything else is rejected so
15
+ * the caller can fall back to the expression path rather than corrupting audio.
16
+ */
17
+
18
+ import { readFileSync, renameSync, writeFileSync } from "fs";
19
+ import { randomBytes } from "crypto";
20
+ import type { AudioVolumeKeyframe } from "./audioMixer.types.js";
21
+ import { normaliseEnvelope } from "@hyperframes/core/media-volume-envelope";
22
+
23
+ const PCM_FORMAT = 1; // WAVE_FORMAT_PCM
24
+ const SUPPORTED_BITS = 16;
25
+
26
+ interface WavLayout {
27
+ numChannels: number;
28
+ sampleRate: number;
29
+ dataOffset: number;
30
+ dataSize: number;
31
+ }
32
+
33
+ /**
34
+ * Locate the `fmt ` and `data` chunks and validate the format we know how to edit.
35
+ *
36
+ * Scans every chunk rather than assuming an ordering: the loop always advances
37
+ * past a chunk's body (using its declared size), so `data` may precede `fmt `
38
+ * and trailing chunks (LIST/fact/etc.) are skipped harmlessly. Returns null on
39
+ * anything unexpected so the caller falls back to the expression path.
40
+ */
41
+ function parseWavLayout(buffer: Buffer): WavLayout | null {
42
+ if (buffer.length < 12 || buffer.toString("ascii", 0, 4) !== "RIFF") return null;
43
+ if (buffer.toString("ascii", 8, 12) !== "WAVE") return null;
44
+
45
+ let offset = 12;
46
+ let fmt: { numChannels: number; sampleRate: number; bitsPerSample: number } | null = null;
47
+ let data: { offset: number; size: number } | null = null;
48
+
49
+ while (offset + 8 <= buffer.length) {
50
+ const chunkId = buffer.toString("ascii", offset, offset + 4);
51
+ const chunkSize = buffer.readUInt32LE(offset + 4);
52
+ const body = offset + 8;
53
+ if (chunkId === "fmt " && body + 16 <= buffer.length) {
54
+ if (buffer.readUInt16LE(body) !== PCM_FORMAT) return null;
55
+ fmt = {
56
+ numChannels: buffer.readUInt16LE(body + 2),
57
+ sampleRate: buffer.readUInt32LE(body + 4),
58
+ bitsPerSample: buffer.readUInt16LE(body + 14),
59
+ };
60
+ } else if (chunkId === "data") {
61
+ data = { offset: body, size: Math.min(chunkSize, buffer.length - body) };
62
+ }
63
+ // Chunks are word-aligned: an odd size carries a trailing pad byte.
64
+ offset = body + chunkSize + (chunkSize % 2);
65
+ }
66
+
67
+ if (!fmt || !data) return null;
68
+ if (fmt.bitsPerSample !== SUPPORTED_BITS || fmt.numChannels < 1) return null;
69
+ return {
70
+ numChannels: fmt.numChannels,
71
+ sampleRate: fmt.sampleRate,
72
+ dataOffset: data.offset,
73
+ dataSize: data.size,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Multiply a prepared WAV's samples by a time-varying gain envelope in place.
79
+ *
80
+ * @returns `true` if the envelope was applied; `false` if the file isn't the
81
+ * expected 16-bit PCM (caller should fall back to the expression path).
82
+ */
83
+ export function applyVolumeEnvelopeToWav(
84
+ wavPath: string,
85
+ keyframes: AudioVolumeKeyframe[],
86
+ trackStart: number,
87
+ baseVolume: number,
88
+ ): boolean {
89
+ const envelope = normaliseEnvelope(keyframes, trackStart, baseVolume);
90
+ if (envelope.length === 0) return false;
91
+
92
+ try {
93
+ const buffer = readFileSync(wavPath);
94
+ const layout = parseWavLayout(buffer);
95
+ if (!layout) return false;
96
+
97
+ const { numChannels, sampleRate, dataOffset, dataSize } = layout;
98
+ const bytesPerSample = SUPPORTED_BITS / 8;
99
+ const frameBytes = numChannels * bytesPerSample;
100
+ const frameCount = Math.floor(dataSize / frameBytes);
101
+
102
+ // Maintain an incremental segment cursor so the per-frame envelope lookup
103
+ // is O(N+M) overall, not O(N×M). interpolateVolumeGain restarts from 0 on
104
+ // each call — fine for the preview path (one call per RAF tick) but not for
105
+ // the PCM path (one call per sample, 48k×duration frames total).
106
+ let segment = 0;
107
+ for (let frame = 0; frame < frameCount; frame += 1) {
108
+ const time = frame / sampleRate;
109
+ while (segment < envelope.length - 2 && time >= envelope[segment + 1]!.time) segment += 1;
110
+
111
+ const a = envelope[segment]!;
112
+ const b = envelope[segment + 1] ?? a;
113
+ const span = b.time - a.time;
114
+ const progress = span <= 0 ? 0 : Math.min(1, Math.max(0, (time - a.time) / span));
115
+ const gain = a.volume + (b.volume - a.volume) * progress;
116
+
117
+ const base = dataOffset + frame * frameBytes;
118
+ for (let channel = 0; channel < numChannels; channel += 1) {
119
+ const at = base + channel * bytesPerSample;
120
+ const scaled = Math.round(buffer.readInt16LE(at) * gain);
121
+ buffer.writeInt16LE(scaled < -32768 ? -32768 : scaled > 32767 ? 32767 : scaled, at);
122
+ }
123
+ }
124
+
125
+ // Write to a uniquely-named sibling then atomically rename over the
126
+ // original. The random name avoids following a pre-planted symlink at a
127
+ // predictable path, and the rename means a crash mid-write can't leave a
128
+ // truncated WAV for the downstream mix.
129
+ const tempPath = `${wavPath}.${randomBytes(6).toString("hex")}.tmp`;
130
+ writeFileSync(tempPath, buffer);
131
+ renameSync(tempPath, wavPath);
132
+ return true;
133
+ } catch {
134
+ // Any read/parse/write failure → leave the file untouched and let the
135
+ // caller fall back to the ffmpeg expression path rather than losing audio.
136
+ return false;
137
+ }
138
+ }