@hyperframes/producer 0.8.192 → 0.8.193

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.
@@ -1384,18 +1384,9 @@ var RenderExecutionContext = class {
1384
1384
  };
1385
1385
 
1386
1386
  // src/services/render/artifactTransaction.ts
1387
- import {
1388
- closeSync as closeSync2,
1389
- existsSync as existsSync5,
1390
- fstatSync as fstatSync2,
1391
- openSync as openSync2,
1392
- readSync,
1393
- readdirSync as readdirSync2,
1394
- renameSync,
1395
- rmSync as rmSync3
1396
- } from "node:fs";
1387
+ import { closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1397
1388
  import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
1398
- import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST } from "@hyperframes/engine";
1389
+ import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
1399
1390
 
1400
1391
  // src/utils/ffprobe.ts
1401
1392
  import {
@@ -1514,7 +1505,7 @@ function createRenderWorkDir(prefix, outputDir) {
1514
1505
  // src/services/render/artifactTransaction.ts
1515
1506
  var defaultFileSystem = {
1516
1507
  existsSync: existsSync5,
1517
- renameSync,
1508
+ renameSync: (source, destination) => renameWithRetry(source, destination),
1518
1509
  rmSync: rmSync3
1519
1510
  };
1520
1511
  async function defaultArtifactDurationProbe(path) {
@@ -1913,58 +1904,10 @@ import {
1913
1904
 
1914
1905
  // src/services/render/captureStageError.ts
1915
1906
  import { CaptureFailure, classifyCaptureFailure } from "@hyperframes/engine";
1916
- var CaptureStageError = class extends CaptureFailure {
1917
- browserConsole;
1918
- constructor(input) {
1919
- const classified = classifyCaptureFailure(input.cause);
1920
- super({
1921
- kind: classified.kind,
1922
- message: normalizeErrorMessage(input.cause),
1923
- cause: input.cause,
1924
- workerDiagnostics: classified.workerDiagnostics
1925
- });
1926
- this.name = "CaptureStageError";
1927
- this.browserConsole = input.browserConsole.slice();
1928
- if (input.cause instanceof Error && input.cause.stack) {
1929
- this.stack = input.cause.stack;
1930
- }
1931
- }
1932
- };
1933
- function wrapCaptureStageError(error, browserConsole) {
1934
- if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
1935
- return new CaptureStageError({ cause: error, browserConsole });
1936
- }
1937
- function getCaptureStageBrowserConsole(error) {
1938
- if (error instanceof CaptureStageError) return error.browserConsole;
1939
- return [];
1940
- }
1941
-
1942
- // src/services/render/stages/captureHdrFrameShared.ts
1943
- import {
1944
- applyDomLayerMask as applyDomLayerMask2,
1945
- blitRgba8OverRgb48le as blitRgba8OverRgb48le2,
1946
- captureAlphaPng as captureAlphaPng2,
1947
- decodePng as decodePng2,
1948
- queryElementStacking as queryElementStacking2,
1949
- removeDomLayerMask as removeDomLayerMask2
1950
- } from "@hyperframes/engine";
1951
1907
 
1952
- // src/services/hdrCompositor.ts
1953
- import { readSync as readSync2, closeSync as closeSync3 } from "fs";
1954
- import { join as join6 } from "path";
1955
- import {
1956
- captureAlphaPng,
1957
- applyDomLayerMask,
1958
- removeDomLayerMask,
1959
- decodePng,
1960
- blitRgba8OverRgb48le,
1961
- blitRgb48leRegion,
1962
- groupIntoLayers,
1963
- blitRgb48leAffine,
1964
- parseTransformMatrix,
1965
- convertTransfer,
1966
- isVideoHiddenBeforeStreamStart
1967
- } from "@hyperframes/engine";
1908
+ // src/services/render/perfSummary.ts
1909
+ import { arch, cpus, platform, totalmem } from "node:os";
1910
+ import { fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
1968
1911
 
1969
1912
  // src/services/render/hdrPerf.ts
1970
1913
  function createHdrPerfCollector() {
@@ -2070,7 +2013,280 @@ function finalizeHdrPerf(perf) {
2070
2013
  };
2071
2014
  }
2072
2015
 
2016
+ // src/services/render/perfSummary.ts
2017
+ function worstSubTimelineWaitOutcome(perfs) {
2018
+ const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
2019
+ if (outcomes.length === 0) return void 0;
2020
+ if (outcomes.includes("script_failure")) return "script_failure";
2021
+ if (outcomes.includes("timeout")) return "timeout";
2022
+ return "ready";
2023
+ }
2024
+ function pushWorkerDedupPerfs(results, sink) {
2025
+ for (const r of results) {
2026
+ if (r.perf) sink.push(r.perf);
2027
+ }
2028
+ }
2029
+ function pushWorkerPageErrors(results, sink) {
2030
+ for (const result of results) {
2031
+ for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
2032
+ }
2033
+ }
2034
+ function withWorkerPageErrors(consoleLines, workerLines) {
2035
+ return [...consoleLines, ...workerLines.filter((line) => !consoleLines.includes(line))];
2036
+ }
2037
+ function roundDb(value) {
2038
+ return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
2039
+ }
2040
+ function aggregateDrawElement(perfs, de) {
2041
+ if (perfs.length === 0) return void 0;
2042
+ const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
2043
+ const gateReasons = [
2044
+ ...new Set(
2045
+ [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
2046
+ )
2047
+ ].sort();
2048
+ const gpuRenderers = [
2049
+ ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
2050
+ ].sort();
2051
+ const drain = de.drainStats;
2052
+ return {
2053
+ mode: modes.join("|") || "unknown",
2054
+ compileGate: de.compileGate,
2055
+ clampReason: de.clampReason,
2056
+ workerInversion: de.workerInversion ?? "none",
2057
+ preInversionWorkers: de.preInversionWorkers,
2058
+ compositionElementCount: de.compositionElementCount,
2059
+ compositionElementCountSource: de.compositionElementCountSource,
2060
+ compositionElementTags: de.compositionElementTags,
2061
+ arollVideoCount: de.arollVideoCount,
2062
+ heygenVideoCount: de.heygenVideoCount,
2063
+ adaptersUsed: de.adaptersUsed,
2064
+ audioCount: de.audioCount,
2065
+ imageCount: de.imageCount,
2066
+ subCompositionCount: de.subCompositionCount,
2067
+ audioGroupCount: de.audioGroupCount,
2068
+ colorGradingCount: de.colorGradingCount,
2069
+ hasLut: de.hasLut,
2070
+ rootBodyMismatch: de.rootBodyMismatch,
2071
+ rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
2072
+ vfxHostCount: de.vfxHostCount,
2073
+ vfxCapture: de.vfxCapture,
2074
+ vfxTypes: de.vfxTypes,
2075
+ shortBand: de.shortBand,
2076
+ parallelRouter: de.parallelRouter ?? "none",
2077
+ preRouterWorkers: de.preRouterWorkers,
2078
+ gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
2079
+ gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
2080
+ workerEncode: perfs.some((p) => p.deWorkerEncode),
2081
+ verifyArmed: perfs.reduce((sum, p) => sum + (p.deVerifyArmed ?? 0), 0),
2082
+ verifyChecked: drain?.verifyChecked ?? 0,
2083
+ verifyMinDb: roundDb(drain?.verifyMinDb),
2084
+ verifyInitMs: perfs.reduce((sum, p) => sum + (p.deVerifyInitMs ?? 0), 0),
2085
+ selfVerifyFallback: de.selfVerifyFallback,
2086
+ fallbackReason: de.fallbackReason,
2087
+ fallbackFailedDb: roundDb(de.fallbackFailedDb),
2088
+ fallbackFrameIndex: de.fallbackFrameIndex,
2089
+ fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
2090
+ blankSuspects: drain?.blankSuspects ?? 0,
2091
+ blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
2092
+ blankRecaptures: drain?.blankRecaptures ?? 0,
2093
+ boundaryFrames: perfs.reduce((sum, p) => sum + (p.deBoundaryFrames ?? 0), 0),
2094
+ ncprFallbacks: perfs.reduce((sum, p) => sum + (p.deNcprFallbacks ?? 0), 0),
2095
+ frameTimeouts: perfs.reduce((sum, p) => sum + (p.deFrameTimeouts ?? 0), 0)
2096
+ };
2097
+ }
2098
+ function maxDefined(values) {
2099
+ const present = values.filter((v) => typeof v === "number");
2100
+ return present.length > 0 ? Math.max(...present) : void 0;
2101
+ }
2102
+ function aggregateChromeMemory(perfs) {
2103
+ const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
2104
+ if (sampled.length === 0) return void 0;
2105
+ return {
2106
+ browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
2107
+ rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
2108
+ // Last samples are per session; summing approximates the whole fleet's
2109
+ // footprint at the end of capture (workers run concurrently).
2110
+ rssLastMb: sampled.reduce((sum, p) => sum + (p.chromeRssLastMb ?? 0), 0),
2111
+ gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
2112
+ samples: sampled.reduce((sum, p) => sum + (p.chromeMemorySamples ?? 0), 0)
2113
+ };
2114
+ }
2115
+ function aggregateDedup(perfs) {
2116
+ if (perfs.length === 0) return void 0;
2117
+ const armed = perfs.some((p) => p.staticDedupArmed);
2118
+ const skipReasons = armed ? [] : [
2119
+ ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
2120
+ ].sort();
2121
+ const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
2122
+ const verificationOutcomes = [
2123
+ ...new Set(
2124
+ verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
2125
+ )
2126
+ ].sort();
2127
+ return {
2128
+ enabled: perfs.some((p) => p.staticDedupEnabled),
2129
+ armed,
2130
+ predictedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupPredicted ?? 0), 0),
2131
+ reusedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupReused ?? 0), 0),
2132
+ skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
2133
+ ...verificationPerfs.length === 0 ? {} : {
2134
+ verifiedFrames: verificationPerfs.reduce(
2135
+ (sum, perf) => sum + (perf.staticDedupVerified ?? 0),
2136
+ 0
2137
+ ),
2138
+ verificationOutcomes,
2139
+ plannedRuns: verificationPerfs.reduce(
2140
+ (sum, perf) => sum + (perf.staticDedupVerificationPlannedRuns ?? 0),
2141
+ 0
2142
+ ),
2143
+ completedRuns: verificationPerfs.reduce(
2144
+ (sum, perf) => sum + (perf.staticDedupVerificationCompletedRuns ?? 0),
2145
+ 0
2146
+ ),
2147
+ screenshots: verificationPerfs.reduce(
2148
+ (sum, perf) => sum + (perf.staticDedupVerificationScreenshots ?? 0),
2149
+ 0
2150
+ ),
2151
+ seeks: verificationPerfs.reduce(
2152
+ (sum, perf) => sum + (perf.staticDedupVerificationSeeks ?? 0),
2153
+ 0
2154
+ ),
2155
+ comparisons: verificationPerfs.reduce(
2156
+ (sum, perf) => sum + (perf.staticDedupVerificationComparisons ?? 0),
2157
+ 0
2158
+ ),
2159
+ verificationElapsedMs: verificationPerfs.reduce(
2160
+ (sum, perf) => sum + (perf.staticDedupVerificationElapsedMs ?? 0),
2161
+ 0
2162
+ )
2163
+ }
2164
+ };
2165
+ }
2166
+ function aggregateBeginFrameReuse(perfs) {
2167
+ const noDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameNoDamage ?? 0), 0);
2168
+ const hasDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameHasDamage ?? 0), 0);
2169
+ if (noDamageFrames + hasDamageFrames === 0) return void 0;
2170
+ return { noDamageFrames, hasDamageFrames };
2171
+ }
2172
+ function buildRenderPerfSummary(input) {
2173
+ return {
2174
+ renderId: input.job.id,
2175
+ totalElapsedMs: input.totalElapsedMs,
2176
+ // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
2177
+ // payloads (CLI telemetry, regression-harness reports) where a single
2178
+ // number is friendlier than `{num,den}`. Callers needing the rational
2179
+ // back can read `job.config.fps`.
2180
+ fps: fpsToNumber3(input.job.config.fps),
2181
+ quality: input.job.config.quality,
2182
+ workers: input.workerCount,
2183
+ workerSizing: input.workerSizing,
2184
+ chunkedEncode: input.enableChunkedEncode,
2185
+ chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
2186
+ compositionDurationSeconds: input.compositionDurationSeconds,
2187
+ totalFrames: input.totalFrames,
2188
+ resolution: { width: input.outputWidth, height: input.outputHeight },
2189
+ videoCount: input.videoCount,
2190
+ audioCount: input.audioCount,
2191
+ stages: input.perfStages,
2192
+ videoExtractBreakdown: input.videoExtractBreakdown,
2193
+ tmpPeakBytes: input.tmpPeakBytes,
2194
+ captureCalibration: input.captureCalibration ? {
2195
+ sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
2196
+ p95Ms: input.captureCalibration.estimate.p95Ms,
2197
+ multiplier: input.captureCalibration.estimate.multiplier,
2198
+ reasons: input.captureCalibration.estimate.reasons
2199
+ } : void 0,
2200
+ captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
2201
+ hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
2202
+ hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
2203
+ observability: input.observability,
2204
+ captureAvgMs: input.totalFrames > 0 ? Math.round(
2205
+ (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
2206
+ ) : void 0,
2207
+ subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
2208
+ captureP50Ms: (() => {
2209
+ const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
2210
+ if (withSamples.length === 0) return void 0;
2211
+ return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
2212
+ })(),
2213
+ peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
2214
+ peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
2215
+ staticDedup: aggregateDedup(input.dedupPerfs),
2216
+ chromeMemory: aggregateChromeMemory(input.dedupPerfs),
2217
+ beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
2218
+ drawElement: aggregateDrawElement(
2219
+ input.dedupPerfs,
2220
+ input.drawElement ?? { selfVerifyFallback: false }
2221
+ ),
2222
+ host: {
2223
+ platform: platform(),
2224
+ arch: arch(),
2225
+ cpuCount: cpus().length,
2226
+ totalMemMb: Math.round(totalmem() / (1024 * 1024)),
2227
+ nodeVersion: process.version,
2228
+ gpuDisabled: input.gpuDisabled
2229
+ }
2230
+ };
2231
+ }
2232
+
2233
+ // src/services/render/captureStageError.ts
2234
+ var CaptureStageError = class extends CaptureFailure {
2235
+ browserConsole;
2236
+ constructor(input) {
2237
+ const classified = classifyCaptureFailure(input.cause);
2238
+ super({
2239
+ kind: classified.kind,
2240
+ message: normalizeErrorMessage(input.cause),
2241
+ cause: input.cause,
2242
+ workerDiagnostics: classified.workerDiagnostics,
2243
+ pageErrorLines: classified.pageErrorLines
2244
+ });
2245
+ this.name = "CaptureStageError";
2246
+ this.browserConsole = withWorkerPageErrors(input.browserConsole, [
2247
+ ...classified.pageErrorLines
2248
+ ]);
2249
+ if (input.cause instanceof Error && input.cause.stack) {
2250
+ this.stack = input.cause.stack;
2251
+ }
2252
+ }
2253
+ };
2254
+ function wrapCaptureStageError(error, browserConsole) {
2255
+ if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
2256
+ return new CaptureStageError({ cause: error, browserConsole });
2257
+ }
2258
+ function getCaptureStageBrowserConsole(error) {
2259
+ if (error instanceof CaptureStageError) return error.browserConsole;
2260
+ if (error instanceof CaptureFailure) return [...error.pageErrorLines];
2261
+ return [];
2262
+ }
2263
+
2264
+ // src/services/render/stages/captureHdrFrameShared.ts
2265
+ import {
2266
+ applyDomLayerMask as applyDomLayerMask2,
2267
+ blitRgba8OverRgb48le as blitRgba8OverRgb48le2,
2268
+ captureAlphaPng as captureAlphaPng2,
2269
+ decodePng as decodePng2,
2270
+ queryElementStacking as queryElementStacking2,
2271
+ removeDomLayerMask as removeDomLayerMask2
2272
+ } from "@hyperframes/engine";
2273
+
2073
2274
  // src/services/hdrCompositor.ts
2275
+ import { readSync as readSync2, closeSync as closeSync3 } from "fs";
2276
+ import { join as join6 } from "path";
2277
+ import {
2278
+ captureAlphaPng,
2279
+ applyDomLayerMask,
2280
+ removeDomLayerMask,
2281
+ decodePng,
2282
+ blitRgba8OverRgb48le,
2283
+ blitRgb48leRegion,
2284
+ groupIntoLayers,
2285
+ blitRgb48leAffine,
2286
+ parseTransformMatrix,
2287
+ convertTransfer,
2288
+ isVideoHiddenBeforeStreamStart
2289
+ } from "@hyperframes/engine";
2074
2290
  function countNonZeroAlpha(rgba) {
2075
2291
  let n = 0;
2076
2292
  for (let p = 3; p < rgba.length; p += 4) {
@@ -2537,7 +2753,7 @@ import {
2537
2753
  resolveVideoExtractionWindow,
2538
2754
  runFfmpeg
2539
2755
  } from "@hyperframes/engine";
2540
- import { fpsToFfmpegArg, fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
2756
+ import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
2541
2757
  var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
2542
2758
  function tempDirSafePrefix(id) {
2543
2759
  const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
@@ -2773,7 +2989,7 @@ async function extractHdrVideoFrames(args) {
2773
2989
  const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
2774
2990
  framesDir,
2775
2991
  plannedVideos,
2776
- fpsToNumber3(job.config.fps),
2992
+ fpsToNumber4(job.config.fps),
2777
2993
  log
2778
2994
  );
2779
2995
  const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
@@ -3226,222 +3442,12 @@ import {
3226
3442
  prepareCaptureSessionForReuse,
3227
3443
  spawnStreamingEncoder
3228
3444
  } from "@hyperframes/engine";
3229
-
3230
- // src/services/render/perfSummary.ts
3231
- import { arch, cpus, platform, totalmem } from "node:os";
3232
- import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
3233
- function worstSubTimelineWaitOutcome(perfs) {
3234
- const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
3235
- if (outcomes.length === 0) return void 0;
3236
- if (outcomes.includes("script_failure")) return "script_failure";
3237
- if (outcomes.includes("timeout")) return "timeout";
3238
- return "ready";
3239
- }
3240
- function pushWorkerDedupPerfs(results, sink) {
3241
- for (const r of results) {
3242
- if (r.perf) sink.push(r.perf);
3243
- }
3244
- }
3245
- function roundDb(value) {
3246
- return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
3247
- }
3248
- function aggregateDrawElement(perfs, de) {
3249
- if (perfs.length === 0) return void 0;
3250
- const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
3251
- const gateReasons = [
3252
- ...new Set(
3253
- [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
3254
- )
3255
- ].sort();
3256
- const gpuRenderers = [
3257
- ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
3258
- ].sort();
3259
- const drain = de.drainStats;
3260
- return {
3261
- mode: modes.join("|") || "unknown",
3262
- compileGate: de.compileGate,
3263
- clampReason: de.clampReason,
3264
- workerInversion: de.workerInversion ?? "none",
3265
- preInversionWorkers: de.preInversionWorkers,
3266
- compositionElementCount: de.compositionElementCount,
3267
- compositionElementCountSource: de.compositionElementCountSource,
3268
- compositionElementTags: de.compositionElementTags,
3269
- arollVideoCount: de.arollVideoCount,
3270
- heygenVideoCount: de.heygenVideoCount,
3271
- adaptersUsed: de.adaptersUsed,
3272
- audioCount: de.audioCount,
3273
- imageCount: de.imageCount,
3274
- subCompositionCount: de.subCompositionCount,
3275
- audioGroupCount: de.audioGroupCount,
3276
- colorGradingCount: de.colorGradingCount,
3277
- hasLut: de.hasLut,
3278
- rootBodyMismatch: de.rootBodyMismatch,
3279
- rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
3280
- vfxHostCount: de.vfxHostCount,
3281
- vfxCapture: de.vfxCapture,
3282
- vfxTypes: de.vfxTypes,
3283
- shortBand: de.shortBand,
3284
- parallelRouter: de.parallelRouter ?? "none",
3285
- preRouterWorkers: de.preRouterWorkers,
3286
- gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
3287
- gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
3288
- workerEncode: perfs.some((p) => p.deWorkerEncode),
3289
- verifyArmed: perfs.reduce((sum, p) => sum + (p.deVerifyArmed ?? 0), 0),
3290
- verifyChecked: drain?.verifyChecked ?? 0,
3291
- verifyMinDb: roundDb(drain?.verifyMinDb),
3292
- verifyInitMs: perfs.reduce((sum, p) => sum + (p.deVerifyInitMs ?? 0), 0),
3293
- selfVerifyFallback: de.selfVerifyFallback,
3294
- fallbackReason: de.fallbackReason,
3295
- fallbackFailedDb: roundDb(de.fallbackFailedDb),
3296
- fallbackFrameIndex: de.fallbackFrameIndex,
3297
- fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
3298
- blankSuspects: drain?.blankSuspects ?? 0,
3299
- blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
3300
- blankRecaptures: drain?.blankRecaptures ?? 0,
3301
- boundaryFrames: perfs.reduce((sum, p) => sum + (p.deBoundaryFrames ?? 0), 0),
3302
- ncprFallbacks: perfs.reduce((sum, p) => sum + (p.deNcprFallbacks ?? 0), 0),
3303
- frameTimeouts: perfs.reduce((sum, p) => sum + (p.deFrameTimeouts ?? 0), 0)
3304
- };
3305
- }
3306
- function maxDefined(values) {
3307
- const present = values.filter((v) => typeof v === "number");
3308
- return present.length > 0 ? Math.max(...present) : void 0;
3309
- }
3310
- function aggregateChromeMemory(perfs) {
3311
- const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
3312
- if (sampled.length === 0) return void 0;
3313
- return {
3314
- browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
3315
- rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
3316
- // Last samples are per session; summing approximates the whole fleet's
3317
- // footprint at the end of capture (workers run concurrently).
3318
- rssLastMb: sampled.reduce((sum, p) => sum + (p.chromeRssLastMb ?? 0), 0),
3319
- gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
3320
- samples: sampled.reduce((sum, p) => sum + (p.chromeMemorySamples ?? 0), 0)
3321
- };
3322
- }
3323
- function aggregateDedup(perfs) {
3324
- if (perfs.length === 0) return void 0;
3325
- const armed = perfs.some((p) => p.staticDedupArmed);
3326
- const skipReasons = armed ? [] : [
3327
- ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
3328
- ].sort();
3329
- const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
3330
- const verificationOutcomes = [
3331
- ...new Set(
3332
- verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
3333
- )
3334
- ].sort();
3335
- return {
3336
- enabled: perfs.some((p) => p.staticDedupEnabled),
3337
- armed,
3338
- predictedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupPredicted ?? 0), 0),
3339
- reusedFrames: perfs.reduce((sum, p) => sum + (p.staticDedupReused ?? 0), 0),
3340
- skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
3341
- ...verificationPerfs.length === 0 ? {} : {
3342
- verifiedFrames: verificationPerfs.reduce(
3343
- (sum, perf) => sum + (perf.staticDedupVerified ?? 0),
3344
- 0
3345
- ),
3346
- verificationOutcomes,
3347
- plannedRuns: verificationPerfs.reduce(
3348
- (sum, perf) => sum + (perf.staticDedupVerificationPlannedRuns ?? 0),
3349
- 0
3350
- ),
3351
- completedRuns: verificationPerfs.reduce(
3352
- (sum, perf) => sum + (perf.staticDedupVerificationCompletedRuns ?? 0),
3353
- 0
3354
- ),
3355
- screenshots: verificationPerfs.reduce(
3356
- (sum, perf) => sum + (perf.staticDedupVerificationScreenshots ?? 0),
3357
- 0
3358
- ),
3359
- seeks: verificationPerfs.reduce(
3360
- (sum, perf) => sum + (perf.staticDedupVerificationSeeks ?? 0),
3361
- 0
3362
- ),
3363
- comparisons: verificationPerfs.reduce(
3364
- (sum, perf) => sum + (perf.staticDedupVerificationComparisons ?? 0),
3365
- 0
3366
- ),
3367
- verificationElapsedMs: verificationPerfs.reduce(
3368
- (sum, perf) => sum + (perf.staticDedupVerificationElapsedMs ?? 0),
3369
- 0
3370
- )
3371
- }
3372
- };
3373
- }
3374
- function aggregateBeginFrameReuse(perfs) {
3375
- const noDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameNoDamage ?? 0), 0);
3376
- const hasDamageFrames = perfs.reduce((sum, p) => sum + (p.beginFrameHasDamage ?? 0), 0);
3377
- if (noDamageFrames + hasDamageFrames === 0) return void 0;
3378
- return { noDamageFrames, hasDamageFrames };
3379
- }
3380
- function buildRenderPerfSummary(input) {
3381
- return {
3382
- renderId: input.job.id,
3383
- totalElapsedMs: input.totalElapsedMs,
3384
- // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
3385
- // payloads (CLI telemetry, regression-harness reports) where a single
3386
- // number is friendlier than `{num,den}`. Callers needing the rational
3387
- // back can read `job.config.fps`.
3388
- fps: fpsToNumber4(input.job.config.fps),
3389
- quality: input.job.config.quality,
3390
- workers: input.workerCount,
3391
- workerSizing: input.workerSizing,
3392
- chunkedEncode: input.enableChunkedEncode,
3393
- chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
3394
- compositionDurationSeconds: input.compositionDurationSeconds,
3395
- totalFrames: input.totalFrames,
3396
- resolution: { width: input.outputWidth, height: input.outputHeight },
3397
- videoCount: input.videoCount,
3398
- audioCount: input.audioCount,
3399
- stages: input.perfStages,
3400
- videoExtractBreakdown: input.videoExtractBreakdown,
3401
- tmpPeakBytes: input.tmpPeakBytes,
3402
- captureCalibration: input.captureCalibration ? {
3403
- sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
3404
- p95Ms: input.captureCalibration.estimate.p95Ms,
3405
- multiplier: input.captureCalibration.estimate.multiplier,
3406
- reasons: input.captureCalibration.estimate.reasons
3407
- } : void 0,
3408
- captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
3409
- hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
3410
- hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
3411
- observability: input.observability,
3412
- captureAvgMs: input.totalFrames > 0 ? Math.round(
3413
- (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
3414
- ) : void 0,
3415
- subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
3416
- captureP50Ms: (() => {
3417
- const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
3418
- if (withSamples.length === 0) return void 0;
3419
- return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
3420
- })(),
3421
- peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
3422
- peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
3423
- staticDedup: aggregateDedup(input.dedupPerfs),
3424
- chromeMemory: aggregateChromeMemory(input.dedupPerfs),
3425
- beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
3426
- drawElement: aggregateDrawElement(
3427
- input.dedupPerfs,
3428
- input.drawElement ?? { selfVerifyFallback: false }
3429
- ),
3430
- host: {
3431
- platform: platform(),
3432
- arch: arch(),
3433
- cpuCount: cpus().length,
3434
- totalMemMb: Math.round(totalmem() / (1024 * 1024)),
3435
- nodeVersion: process.version,
3436
- gpuDisabled: input.gpuDisabled
3437
- }
3438
- };
3439
- }
3440
-
3441
- // src/services/render/stages/captureStreamingStage.ts
3442
3445
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3443
3446
  var DE_STALL_POLL_MS = 5e3;
3444
3447
  var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
3448
+ function poolFailure(error, probeSession) {
3449
+ return wrapCaptureStageError(error, probeSession?.browserConsoleBuffer ?? []);
3450
+ }
3445
3451
  var ParallelCaptureStallError = class extends Error {
3446
3452
  variant;
3447
3453
  constructor(args) {
@@ -3888,7 +3894,7 @@ async function runCaptureStreamingStage(input) {
3888
3894
  }
3889
3895
  );
3890
3896
  } catch (err) {
3891
- if (parallelDrainError) throw parallelDrainError;
3897
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
3892
3898
  if (stalled && abortSignal?.aborted !== true) {
3893
3899
  throw new ParallelCaptureStallError({
3894
3900
  variant: "pool",
@@ -3899,13 +3905,15 @@ async function runCaptureStreamingStage(input) {
3899
3905
  phaseSummary: phaseSummary()
3900
3906
  });
3901
3907
  }
3902
- throw err;
3908
+ throw poolFailure(err, probeSession);
3903
3909
  } finally {
3904
3910
  clearInterval(stallTimer);
3905
3911
  abortSignal?.removeEventListener("abort", forwardParentAbort);
3906
3912
  }
3907
- if (parallelDrainError) throw parallelDrainError;
3913
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
3908
3914
  pushWorkerDedupPerfs(workerResults, dedupPerfs);
3915
+ const workerPageErrors = [];
3916
+ pushWorkerPageErrors(workerResults, workerPageErrors);
3909
3917
  if (parallelGuardRan) {
3910
3918
  deDrainStats = parallelStats;
3911
3919
  }
@@ -3915,6 +3923,7 @@ async function runCaptureStreamingStage(input) {
3915
3923
  await closeCaptureSession(probeSession);
3916
3924
  probeSession = null;
3917
3925
  }
3926
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
3918
3927
  } else {
3919
3928
  const videoInjector = createRenderVideoFrameInjector();
3920
3929
  const session = probeSession ?? await createCaptureSession(
@@ -4361,8 +4370,9 @@ async function runCaptureSegmentedStage(input) {
4361
4370
 
4362
4371
  // src/services/render/segmentManifest.ts
4363
4372
  import { createHash as createHash2 } from "node:crypto";
4364
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4373
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4365
4374
  import { join as join10 } from "node:path";
4375
+ import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
4366
4376
  var MANIFEST_FILENAME = "segments.json";
4367
4377
  function computeSegmentPlanHash(input) {
4368
4378
  const ordered = [
@@ -4418,7 +4428,7 @@ function writeSegmentManifest(segmentDir, manifest) {
4418
4428
  mkdirSync4(segmentDir, { recursive: true });
4419
4429
  const tmp = join10(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
4420
4430
  writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
4421
- renameSync2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4431
+ renameWithRetry2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4422
4432
  }
4423
4433
  async function probeSegmentFrameCount(path, probe) {
4424
4434
  try {
@@ -5470,19 +5480,11 @@ import {
5470
5480
 
5471
5481
  // src/services/animatedGifPrep.ts
5472
5482
  import { createHash as createHash4 } from "node:crypto";
5473
- import {
5474
- copyFileSync as copyFileSync2,
5475
- existsSync as existsSync8,
5476
- mkdirSync as mkdirSync5,
5477
- readFileSync as readFileSync5,
5478
- renameSync as renameSync3,
5479
- rmSync as rmSync6,
5480
- statSync as statSync4
5481
- } from "node:fs";
5483
+ import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
5482
5484
  import { dirname as dirname5, isAbsolute as isAbsolute2, join as join12, resolve as resolve5 } from "node:path";
5483
5485
  import { parseHTML as parseHTML3 } from "linkedom";
5484
5486
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
5485
- import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2 } from "@hyperframes/engine";
5487
+ import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
5486
5488
  var PREPARED_GIF_SUBDIR = "_animated_gif";
5487
5489
  var CACHE_SCHEMA = "hfgif-v1";
5488
5490
  function splitUrlSuffix(src) {
@@ -5661,7 +5663,7 @@ async function ensurePreparedWebm(input) {
5661
5663
  throw new Error("Animated GIF transcode produced an empty output");
5662
5664
  }
5663
5665
  if (!isUsableFile(input.cachePath)) {
5664
- renameSync3(tmpPath, input.cachePath);
5666
+ renameWithRetry3(tmpPath, input.cachePath);
5665
5667
  } else {
5666
5668
  rmSync6(tmpPath, { force: true });
5667
5669
  }
@@ -8730,7 +8732,7 @@ import {
8730
8732
 
8731
8733
  // src/services/render/audioPadTrim.ts
8732
8734
  import { spawn } from "node:child_process";
8733
- import { mkdtempSync as mkdtempSync3, renameSync as renameSync4, rmSync as rmSync7 } from "node:fs";
8735
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync7 } from "node:fs";
8734
8736
  import { dirname as dirname7, join as join17 } from "node:path";
8735
8737
  import {
8736
8738
  DEFAULT_CONFIG,
@@ -8739,6 +8741,7 @@ import {
8739
8741
  extractAudioMetadata as extractAudioMetadata3,
8740
8742
  getFfprobeBinary,
8741
8743
  ManagedChildProcess,
8744
+ renameWithRetry as renameWithRetry4,
8742
8745
  runFfmpeg as runFfmpeg4,
8743
8746
  trackChildProcess
8744
8747
  } from "@hyperframes/engine";
@@ -8994,7 +8997,7 @@ async function enforceAacTruePeak(input) {
8994
8997
  }
8995
8998
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
8996
8999
  if (measuredPath !== correctedPath) return { success: true };
8997
- renameSync4(correctedPath, input.audioPath);
9000
+ renameWithRetry4(correctedPath, input.audioPath);
8998
9001
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
8999
9002
  }
9000
9003
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -9184,6 +9187,7 @@ async function runAudioStage(input) {
9184
9187
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
9185
9188
  let audioLoweredDb;
9186
9189
  if (result.hasAudio) {
9190
+ const guardStart = Date.now();
9187
9191
  const peak = await constrainAacTruePeak({
9188
9192
  audioPath: result.audioOutputPath,
9189
9193
  sourcePath: pcmSourcePath,
@@ -9193,6 +9197,7 @@ async function runAudioStage(input) {
9193
9197
  });
9194
9198
  if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
9195
9199
  audioLoweredDb = peak.loweredDb;
9200
+ input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
9196
9201
  }
9197
9202
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9198
9203
  } finally {
@@ -9218,6 +9223,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9218
9223
  let hasAudio = false;
9219
9224
  let audioError;
9220
9225
  let audioFailures;
9226
+ let audioPhaseMs;
9221
9227
  if (audios.length > 0) {
9222
9228
  let audioResult;
9223
9229
  try {
@@ -9267,6 +9273,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9267
9273
  assertAudioNotInterrupted(audioResult.failures);
9268
9274
  hasAudio = audioResult.success;
9269
9275
  audioFailures = audioResult.failures;
9276
+ audioPhaseMs = audioResult.phaseMs;
9270
9277
  if (!hasAudio) {
9271
9278
  audioError = audioResult.error ?? "audio mix failed for an unknown reason";
9272
9279
  for (const failure of audioResult.failures ?? []) {
@@ -9285,7 +9292,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9285
9292
  audioOutputPath,
9286
9293
  hasAudio,
9287
9294
  audioError,
9288
- audioFailures
9295
+ audioFailures,
9296
+ audioPhaseMs
9289
9297
  };
9290
9298
  }
9291
9299
 
@@ -9414,6 +9422,7 @@ async function runCaptureStage(input) {
9414
9422
  let { workerCount } = plan;
9415
9423
  const { forceScreenshot } = plan;
9416
9424
  let lastBrowserConsole = [];
9425
+ const workerPageErrors = [];
9417
9426
  let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
9418
9427
  const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
9419
9428
  if (frameRange !== void 0) {
@@ -9451,6 +9460,7 @@ async function runCaptureStage(input) {
9451
9460
  abortSignal,
9452
9461
  frameRangeStart: frameRange?.startFrame,
9453
9462
  dedupPerfs,
9463
+ pageErrorLines: workerPageErrors,
9454
9464
  onProgress: (progress) => {
9455
9465
  if (progress.latestWorkerPhase) {
9456
9466
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
@@ -9471,6 +9481,11 @@ async function runCaptureStage(input) {
9471
9481
  },
9472
9482
  cfg: captureCfg,
9473
9483
  log
9484
+ }).catch((error) => {
9485
+ throw wrapCaptureStageError(
9486
+ error,
9487
+ withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
9488
+ );
9474
9489
  });
9475
9490
  captureAttempts.push(...attempts);
9476
9491
  const lastAttempt = attempts[attempts.length - 1];
@@ -9483,6 +9498,7 @@ async function runCaptureStage(input) {
9483
9498
  await closeCaptureSession5(probeSession);
9484
9499
  probeSession = null;
9485
9500
  }
9501
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
9486
9502
  } else {
9487
9503
  const sequential = await runSequentialDiskCapture(
9488
9504
  input,
@@ -10798,7 +10814,7 @@ function videoEncoderOptions(input, engineCfg) {
10798
10814
  };
10799
10815
  }
10800
10816
  function canEncodeWhileCapturing(input, workerCount, cpuCount) {
10801
- return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg" && input.lockGopForChunkConcat !== true;
10817
+ return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg";
10802
10818
  }
10803
10819
  async function readLandedFrame(workerPath, mergedPath) {
10804
10820
  if (workerPath !== void 0) {
@@ -11453,6 +11469,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11453
11469
  options.cfg
11454
11470
  );
11455
11471
  pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
11472
+ if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
11456
11473
  } finally {
11457
11474
  await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
11458
11475
  }
@@ -14069,7 +14086,9 @@ import {
14069
14086
  getEncoderPreset as getEncoderPreset4,
14070
14087
  normalizeVp9CpuUsed,
14071
14088
  resolveConfig as resolveConfig3,
14072
- declareContentAddressedUrl
14089
+ declareContentAddressedUrl,
14090
+ renameWithRetry as renameWithRetry6,
14091
+ declareProbeFactsUrl
14073
14092
  } from "@hyperframes/engine";
14074
14093
 
14075
14094
  // src/services/distributed/planFrameCost.ts
@@ -14139,6 +14158,7 @@ var FIELD_DELIMITER = Buffer.from([0]);
14139
14158
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
14140
14159
 
14141
14160
  // src/services/distributed/mp4Stitch.ts
14161
+ import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
14142
14162
  var SCAN_BYTES = 64 * 1024;
14143
14163
 
14144
14164
  // src/services/distributed/chunkAudio.ts