@hyperframes/engine 0.8.162 → 0.8.163

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.
@@ -892,6 +892,7 @@ async function mixThroughMasterBus(tracks, masterChain, outputPath, workDir, tot
892
892
  export async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config, compiledDir) {
893
893
  const startMs = Date.now();
894
894
  const tracks = [];
895
+ const preparedTracks = new Array(elements.length);
895
896
  const failures = [];
896
897
  // Grouped members are diverted here instead of `tracks` during element
897
898
  // processing, then summed into one processed track per group before the
@@ -922,7 +923,7 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
922
923
  else
923
924
  signal.addEventListener("abort", () => internalController.abort(), { once: true });
924
925
  }
925
- await Promise.all(elements.map(async (element) => {
926
+ await Promise.all(elements.map(async (element, index) => {
926
927
  if (effectiveSignal.aborted) {
927
928
  failures.push({
928
929
  stage: "cancelled",
@@ -1097,27 +1098,7 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
1097
1098
  ...(element.fadeOut ? { fadeOut: element.fadeOut } : {}),
1098
1099
  ...(tailSeconds > 0 ? { tailSeconds } : {}),
1099
1100
  };
1100
- // A grouped member keeps every bit of its OWN processing above
1101
- // (FX, envelope) exactly like an ungrouped track — it just lands in
1102
- // the group's bucket instead of the flat list, to be summed into one
1103
- // processed bus below rather than mixed directly.
1104
- if (element.groupId) {
1105
- const bucket = groupTracks.get(element.groupId);
1106
- if (bucket)
1107
- bucket.push(track);
1108
- else
1109
- groupTracks.set(element.groupId, [track]);
1110
- if (!groupMeta.has(element.groupId)) {
1111
- groupMeta.set(element.groupId, {
1112
- ...(element.groupFxChain ? { fxChain: element.groupFxChain } : {}),
1113
- ...(element.groupAutomation ? { automation: element.groupAutomation } : {}),
1114
- volume: element.groupVolume ?? 1,
1115
- });
1116
- }
1117
- }
1118
- else {
1119
- tracks.push(track);
1120
- }
1101
+ preparedTracks[index] = track;
1121
1102
  }
1122
1103
  catch (err) {
1123
1104
  // An FX failure is fatal for the whole mix. Every other failure mode
@@ -1147,6 +1128,30 @@ export async function processCompositionAudio(elements, baseDir, workDir, output
1147
1128
  }
1148
1129
  throw err;
1149
1130
  });
1131
+ // FFmpeg's floating-point sum depends on input order. Keep parallel prep,
1132
+ // but assemble flat tracks, group members and groups in authored order.
1133
+ elements.forEach((element, index) => {
1134
+ const track = preparedTracks[index];
1135
+ if (!track)
1136
+ return;
1137
+ if (element.groupId) {
1138
+ const bucket = groupTracks.get(element.groupId);
1139
+ if (bucket)
1140
+ bucket.push(track);
1141
+ else
1142
+ groupTracks.set(element.groupId, [track]);
1143
+ if (!groupMeta.has(element.groupId)) {
1144
+ groupMeta.set(element.groupId, {
1145
+ ...(element.groupFxChain ? { fxChain: element.groupFxChain } : {}),
1146
+ ...(element.groupAutomation ? { automation: element.groupAutomation } : {}),
1147
+ volume: element.groupVolume ?? 1,
1148
+ });
1149
+ }
1150
+ }
1151
+ else {
1152
+ tracks.push(track);
1153
+ }
1154
+ });
1150
1155
  // Never turn a per-track preparation failure into a successful partial mix.
1151
1156
  // The producer only surfaces audio failures when `success` is false; mixing
1152
1157
  // the remaining tracks made the omitted cue indistinguishable from a valid
@@ -48,5 +48,5 @@ export declare function ffmpegStatsReader(onStats: (stats: FfmpegStats) => void)
48
48
  */
49
49
  export declare function formatFfmpegError(exitCode: number | null, stderr: string, tailLines?: number): string;
50
50
  /** formatFfmpegError, led by the timeout when this process was killed at its deadline. */
51
- export declare function describeFfmpegFailure(result: Pick<RunFfmpegResult, "exitCode" | "stderr" | "terminationReason">, timeoutMs: number): string;
51
+ export declare function describeFfmpegFailure(result: Pick<RunFfmpegResult, "exitCode" | "signal" | "stderr" | "terminationReason">, timeoutMs: number): string;
52
52
  export declare function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunFfmpegResult>;
@@ -97,10 +97,16 @@ export function formatFfmpegError(exitCode, stderr, tailLines = DEFAULT_STDERR_T
97
97
  /** formatFfmpegError, led by the timeout when this process was killed at its deadline. */
98
98
  export function describeFfmpegFailure(result, timeoutMs) {
99
99
  const error = formatFfmpegError(result.exitCode, result.stderr);
100
- if (result.terminationReason !== "deadline")
100
+ if (result.terminationReason === "deadline") {
101
+ return (`FFmpeg timed out after ${timeoutMs} ms (ffmpegProcessTimeout; long renders can raise ` +
102
+ `FFMPEG_PROCESS_TIMEOUT_MS). ${error}`);
103
+ }
104
+ if (result.exitCode !== null)
101
105
  return error;
102
- return (`FFmpeg timed out after ${timeoutMs} ms (ffmpegProcessTimeout; long renders can raise ` +
103
- `FFMPEG_PROCESS_TIMEOUT_MS). ${error}`);
106
+ const signalOrRequestedStop = result.terminationReason === "exit" ? result.signal : result.terminationReason;
107
+ return signalOrRequestedStop
108
+ ? `FFmpeg ended without an exit code (${signalOrRequestedStop}). ${error}`
109
+ : error;
104
110
  }
105
111
  function spawnFfmpeg(args) {
106
112
  // windowsHide: ffmpeg/ffprobe are console-subsystem binaries, so without
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.162",
3
+ "version": "0.8.163",
4
4
  "description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,8 +50,8 @@
50
50
  "linkedom": "^0.18.12",
51
51
  "puppeteer": "^25.8.0",
52
52
  "puppeteer-core": "^25.8.0",
53
- "@hyperframes/core": "^0.8.162",
54
- "@hyperframes/parsers": "^0.8.162"
53
+ "@hyperframes/core": "^0.8.163",
54
+ "@hyperframes/parsers": "^0.8.163"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",