@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.
package/dist/index.js CHANGED
@@ -1364,18 +1364,9 @@ var RenderExecutionContext = class {
1364
1364
  };
1365
1365
 
1366
1366
  // src/services/render/artifactTransaction.ts
1367
- import {
1368
- closeSync as closeSync2,
1369
- existsSync as existsSync5,
1370
- fstatSync as fstatSync2,
1371
- openSync as openSync2,
1372
- readSync,
1373
- readdirSync as readdirSync2,
1374
- renameSync,
1375
- rmSync as rmSync3
1376
- } from "node:fs";
1367
+ import { closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1377
1368
  import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
1378
- import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST } from "@hyperframes/engine";
1369
+ import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
1379
1370
 
1380
1371
  // src/utils/ffprobe.ts
1381
1372
  import {
@@ -1495,7 +1486,7 @@ function createRenderWorkDir(prefix, outputDir) {
1495
1486
  // src/services/render/artifactTransaction.ts
1496
1487
  var defaultFileSystem = {
1497
1488
  existsSync: existsSync5,
1498
- renameSync,
1489
+ renameSync: (source, destination) => renameWithRetry(source, destination),
1499
1490
  rmSync: rmSync3
1500
1491
  };
1501
1492
  async function defaultArtifactDurationProbe(path) {
@@ -1894,58 +1885,10 @@ import {
1894
1885
 
1895
1886
  // src/services/render/captureStageError.ts
1896
1887
  import { CaptureFailure, classifyCaptureFailure } from "@hyperframes/engine";
1897
- var CaptureStageError = class extends CaptureFailure {
1898
- browserConsole;
1899
- constructor(input) {
1900
- const classified = classifyCaptureFailure(input.cause);
1901
- super({
1902
- kind: classified.kind,
1903
- message: normalizeErrorMessage(input.cause),
1904
- cause: input.cause,
1905
- workerDiagnostics: classified.workerDiagnostics
1906
- });
1907
- this.name = "CaptureStageError";
1908
- this.browserConsole = input.browserConsole.slice();
1909
- if (input.cause instanceof Error && input.cause.stack) {
1910
- this.stack = input.cause.stack;
1911
- }
1912
- }
1913
- };
1914
- function wrapCaptureStageError(error, browserConsole) {
1915
- if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
1916
- return new CaptureStageError({ cause: error, browserConsole });
1917
- }
1918
- function getCaptureStageBrowserConsole(error) {
1919
- if (error instanceof CaptureStageError) return error.browserConsole;
1920
- return [];
1921
- }
1922
-
1923
- // src/services/render/stages/captureHdrFrameShared.ts
1924
- import {
1925
- applyDomLayerMask as applyDomLayerMask2,
1926
- blitRgba8OverRgb48le as blitRgba8OverRgb48le2,
1927
- captureAlphaPng as captureAlphaPng2,
1928
- decodePng as decodePng2,
1929
- queryElementStacking as queryElementStacking2,
1930
- removeDomLayerMask as removeDomLayerMask2
1931
- } from "@hyperframes/engine";
1932
1888
 
1933
- // src/services/hdrCompositor.ts
1934
- import { readSync as readSync2, closeSync as closeSync3 } from "fs";
1935
- import { join as join6 } from "path";
1936
- import {
1937
- captureAlphaPng,
1938
- applyDomLayerMask,
1939
- removeDomLayerMask,
1940
- decodePng,
1941
- blitRgba8OverRgb48le,
1942
- blitRgb48leRegion,
1943
- groupIntoLayers,
1944
- blitRgb48leAffine,
1945
- parseTransformMatrix,
1946
- convertTransfer,
1947
- isVideoHiddenBeforeStreamStart
1948
- } from "@hyperframes/engine";
1889
+ // src/services/render/perfSummary.ts
1890
+ import { arch, cpus, platform, totalmem } from "node:os";
1891
+ import { fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
1949
1892
 
1950
1893
  // src/services/render/hdrPerf.ts
1951
1894
  function createHdrPerfCollector() {
@@ -2051,7 +1994,280 @@ function finalizeHdrPerf(perf) {
2051
1994
  };
2052
1995
  }
2053
1996
 
1997
+ // src/services/render/perfSummary.ts
1998
+ function worstSubTimelineWaitOutcome(perfs) {
1999
+ const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
2000
+ if (outcomes.length === 0) return void 0;
2001
+ if (outcomes.includes("script_failure")) return "script_failure";
2002
+ if (outcomes.includes("timeout")) return "timeout";
2003
+ return "ready";
2004
+ }
2005
+ function pushWorkerDedupPerfs(results, sink) {
2006
+ for (const r of results) {
2007
+ if (r.perf) sink.push(r.perf);
2008
+ }
2009
+ }
2010
+ function pushWorkerPageErrors(results, sink) {
2011
+ for (const result of results) {
2012
+ for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
2013
+ }
2014
+ }
2015
+ function withWorkerPageErrors(consoleLines, workerLines) {
2016
+ return [...consoleLines, ...workerLines.filter((line) => !consoleLines.includes(line))];
2017
+ }
2018
+ function roundDb(value) {
2019
+ return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
2020
+ }
2021
+ function aggregateDrawElement(perfs, de) {
2022
+ if (perfs.length === 0) return void 0;
2023
+ const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
2024
+ const gateReasons = [
2025
+ ...new Set(
2026
+ [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
2027
+ )
2028
+ ].sort();
2029
+ const gpuRenderers = [
2030
+ ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
2031
+ ].sort();
2032
+ const drain = de.drainStats;
2033
+ return {
2034
+ mode: modes.join("|") || "unknown",
2035
+ compileGate: de.compileGate,
2036
+ clampReason: de.clampReason,
2037
+ workerInversion: de.workerInversion ?? "none",
2038
+ preInversionWorkers: de.preInversionWorkers,
2039
+ compositionElementCount: de.compositionElementCount,
2040
+ compositionElementCountSource: de.compositionElementCountSource,
2041
+ compositionElementTags: de.compositionElementTags,
2042
+ arollVideoCount: de.arollVideoCount,
2043
+ heygenVideoCount: de.heygenVideoCount,
2044
+ adaptersUsed: de.adaptersUsed,
2045
+ audioCount: de.audioCount,
2046
+ imageCount: de.imageCount,
2047
+ subCompositionCount: de.subCompositionCount,
2048
+ audioGroupCount: de.audioGroupCount,
2049
+ colorGradingCount: de.colorGradingCount,
2050
+ hasLut: de.hasLut,
2051
+ rootBodyMismatch: de.rootBodyMismatch,
2052
+ rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
2053
+ vfxHostCount: de.vfxHostCount,
2054
+ vfxCapture: de.vfxCapture,
2055
+ vfxTypes: de.vfxTypes,
2056
+ shortBand: de.shortBand,
2057
+ parallelRouter: de.parallelRouter ?? "none",
2058
+ preRouterWorkers: de.preRouterWorkers,
2059
+ gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
2060
+ gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
2061
+ workerEncode: perfs.some((p) => p.deWorkerEncode),
2062
+ verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
2063
+ verifyChecked: drain?.verifyChecked ?? 0,
2064
+ verifyMinDb: roundDb(drain?.verifyMinDb),
2065
+ verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
2066
+ selfVerifyFallback: de.selfVerifyFallback,
2067
+ fallbackReason: de.fallbackReason,
2068
+ fallbackFailedDb: roundDb(de.fallbackFailedDb),
2069
+ fallbackFrameIndex: de.fallbackFrameIndex,
2070
+ fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
2071
+ blankSuspects: drain?.blankSuspects ?? 0,
2072
+ blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
2073
+ blankRecaptures: drain?.blankRecaptures ?? 0,
2074
+ boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
2075
+ ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
2076
+ frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
2077
+ };
2078
+ }
2079
+ function maxDefined(values) {
2080
+ const present = values.filter((v) => typeof v === "number");
2081
+ return present.length > 0 ? Math.max(...present) : void 0;
2082
+ }
2083
+ function aggregateChromeMemory(perfs) {
2084
+ const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
2085
+ if (sampled.length === 0) return void 0;
2086
+ return {
2087
+ browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
2088
+ rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
2089
+ // Last samples are per session; summing approximates the whole fleet's
2090
+ // footprint at the end of capture (workers run concurrently).
2091
+ rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
2092
+ gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
2093
+ samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
2094
+ };
2095
+ }
2096
+ function aggregateDedup(perfs) {
2097
+ if (perfs.length === 0) return void 0;
2098
+ const armed = perfs.some((p) => p.staticDedupArmed);
2099
+ const skipReasons = armed ? [] : [
2100
+ ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
2101
+ ].sort();
2102
+ const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
2103
+ const verificationOutcomes = [
2104
+ ...new Set(
2105
+ verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
2106
+ )
2107
+ ].sort();
2108
+ return {
2109
+ enabled: perfs.some((p) => p.staticDedupEnabled),
2110
+ armed,
2111
+ predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
2112
+ reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
2113
+ skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
2114
+ ...verificationPerfs.length === 0 ? {} : {
2115
+ verifiedFrames: verificationPerfs.reduce(
2116
+ (sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
2117
+ 0
2118
+ ),
2119
+ verificationOutcomes,
2120
+ plannedRuns: verificationPerfs.reduce(
2121
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
2122
+ 0
2123
+ ),
2124
+ completedRuns: verificationPerfs.reduce(
2125
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
2126
+ 0
2127
+ ),
2128
+ screenshots: verificationPerfs.reduce(
2129
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
2130
+ 0
2131
+ ),
2132
+ seeks: verificationPerfs.reduce(
2133
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
2134
+ 0
2135
+ ),
2136
+ comparisons: verificationPerfs.reduce(
2137
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
2138
+ 0
2139
+ ),
2140
+ verificationElapsedMs: verificationPerfs.reduce(
2141
+ (sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
2142
+ 0
2143
+ )
2144
+ }
2145
+ };
2146
+ }
2147
+ function aggregateBeginFrameReuse(perfs) {
2148
+ const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
2149
+ const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
2150
+ if (noDamageFrames + hasDamageFrames === 0) return void 0;
2151
+ return { noDamageFrames, hasDamageFrames };
2152
+ }
2153
+ function buildRenderPerfSummary(input) {
2154
+ return {
2155
+ renderId: input.job.id,
2156
+ totalElapsedMs: input.totalElapsedMs,
2157
+ // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
2158
+ // payloads (CLI telemetry, regression-harness reports) where a single
2159
+ // number is friendlier than `{num,den}`. Callers needing the rational
2160
+ // back can read `job.config.fps`.
2161
+ fps: fpsToNumber3(input.job.config.fps),
2162
+ quality: input.job.config.quality,
2163
+ workers: input.workerCount,
2164
+ workerSizing: input.workerSizing,
2165
+ chunkedEncode: input.enableChunkedEncode,
2166
+ chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
2167
+ compositionDurationSeconds: input.compositionDurationSeconds,
2168
+ totalFrames: input.totalFrames,
2169
+ resolution: { width: input.outputWidth, height: input.outputHeight },
2170
+ videoCount: input.videoCount,
2171
+ audioCount: input.audioCount,
2172
+ stages: input.perfStages,
2173
+ videoExtractBreakdown: input.videoExtractBreakdown,
2174
+ tmpPeakBytes: input.tmpPeakBytes,
2175
+ captureCalibration: input.captureCalibration ? {
2176
+ sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
2177
+ p95Ms: input.captureCalibration.estimate.p95Ms,
2178
+ multiplier: input.captureCalibration.estimate.multiplier,
2179
+ reasons: input.captureCalibration.estimate.reasons
2180
+ } : void 0,
2181
+ captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
2182
+ hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
2183
+ hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
2184
+ observability: input.observability,
2185
+ captureAvgMs: input.totalFrames > 0 ? Math.round(
2186
+ (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
2187
+ ) : void 0,
2188
+ subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
2189
+ captureP50Ms: (() => {
2190
+ const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
2191
+ if (withSamples.length === 0) return void 0;
2192
+ return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
2193
+ })(),
2194
+ peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
2195
+ peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
2196
+ staticDedup: aggregateDedup(input.dedupPerfs),
2197
+ chromeMemory: aggregateChromeMemory(input.dedupPerfs),
2198
+ beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
2199
+ drawElement: aggregateDrawElement(
2200
+ input.dedupPerfs,
2201
+ input.drawElement ?? { selfVerifyFallback: false }
2202
+ ),
2203
+ host: {
2204
+ platform: platform(),
2205
+ arch: arch(),
2206
+ cpuCount: cpus().length,
2207
+ totalMemMb: Math.round(totalmem() / (1024 * 1024)),
2208
+ nodeVersion: process.version,
2209
+ gpuDisabled: input.gpuDisabled
2210
+ }
2211
+ };
2212
+ }
2213
+
2214
+ // src/services/render/captureStageError.ts
2215
+ var CaptureStageError = class extends CaptureFailure {
2216
+ browserConsole;
2217
+ constructor(input) {
2218
+ const classified = classifyCaptureFailure(input.cause);
2219
+ super({
2220
+ kind: classified.kind,
2221
+ message: normalizeErrorMessage(input.cause),
2222
+ cause: input.cause,
2223
+ workerDiagnostics: classified.workerDiagnostics,
2224
+ pageErrorLines: classified.pageErrorLines
2225
+ });
2226
+ this.name = "CaptureStageError";
2227
+ this.browserConsole = withWorkerPageErrors(input.browserConsole, [
2228
+ ...classified.pageErrorLines
2229
+ ]);
2230
+ if (input.cause instanceof Error && input.cause.stack) {
2231
+ this.stack = input.cause.stack;
2232
+ }
2233
+ }
2234
+ };
2235
+ function wrapCaptureStageError(error, browserConsole) {
2236
+ if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
2237
+ return new CaptureStageError({ cause: error, browserConsole });
2238
+ }
2239
+ function getCaptureStageBrowserConsole(error) {
2240
+ if (error instanceof CaptureStageError) return error.browserConsole;
2241
+ if (error instanceof CaptureFailure) return [...error.pageErrorLines];
2242
+ return [];
2243
+ }
2244
+
2245
+ // src/services/render/stages/captureHdrFrameShared.ts
2246
+ import {
2247
+ applyDomLayerMask as applyDomLayerMask2,
2248
+ blitRgba8OverRgb48le as blitRgba8OverRgb48le2,
2249
+ captureAlphaPng as captureAlphaPng2,
2250
+ decodePng as decodePng2,
2251
+ queryElementStacking as queryElementStacking2,
2252
+ removeDomLayerMask as removeDomLayerMask2
2253
+ } from "@hyperframes/engine";
2254
+
2054
2255
  // src/services/hdrCompositor.ts
2256
+ import { readSync as readSync2, closeSync as closeSync3 } from "fs";
2257
+ import { join as join6 } from "path";
2258
+ import {
2259
+ captureAlphaPng,
2260
+ applyDomLayerMask,
2261
+ removeDomLayerMask,
2262
+ decodePng,
2263
+ blitRgba8OverRgb48le,
2264
+ blitRgb48leRegion,
2265
+ groupIntoLayers,
2266
+ blitRgb48leAffine,
2267
+ parseTransformMatrix,
2268
+ convertTransfer,
2269
+ isVideoHiddenBeforeStreamStart
2270
+ } from "@hyperframes/engine";
2055
2271
  function countNonZeroAlpha(rgba) {
2056
2272
  let n = 0;
2057
2273
  for (let p = 3; p < rgba.length; p += 4) {
@@ -2518,7 +2734,7 @@ import {
2518
2734
  resolveVideoExtractionWindow,
2519
2735
  runFfmpeg
2520
2736
  } from "@hyperframes/engine";
2521
- import { fpsToFfmpegArg, fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
2737
+ import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
2522
2738
  var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
2523
2739
  function tempDirSafePrefix(id) {
2524
2740
  const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
@@ -2754,7 +2970,7 @@ async function extractHdrVideoFrames(args) {
2754
2970
  const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
2755
2971
  framesDir,
2756
2972
  plannedVideos,
2757
- fpsToNumber3(job.config.fps),
2973
+ fpsToNumber4(job.config.fps),
2758
2974
  log
2759
2975
  );
2760
2976
  const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
@@ -3120,309 +3336,99 @@ function cleanupEndedHdrVideos(args) {
3120
3336
  if (time > endTime && !cleanedUpVideos.has(videoId)) {
3121
3337
  const stillNeeded = activeTransition && (sceneElements[activeTransition.fromScene]?.includes(videoId) || sceneElements[activeTransition.toScene]?.includes(videoId));
3122
3338
  if (!stillNeeded) {
3123
- const frameSource = hdrVideoFrameSources.get(videoId);
3124
- if (frameSource) {
3125
- cleanupHdrVideoFrameSource(frameSource, log);
3126
- hdrVideoFrameSources.delete(videoId);
3127
- }
3128
- cleanedUpVideos.add(videoId);
3129
- }
3130
- }
3131
- }
3132
- }
3133
-
3134
- // src/services/render/segmentPlan.ts
3135
- var DEFAULT_SEGMENT_FRAMES = 3e3;
3136
- var MIN_SEGMENT_FRAMES = 30;
3137
- function assertPositiveInteger(name, value) {
3138
- if (!Number.isInteger(value) || value <= 0) {
3139
- throw new Error(`[segmentPlan] ${name} must be a positive integer (got ${String(value)})`);
3140
- }
3141
- }
3142
- function planSegments(totalFrames, segmentFrames) {
3143
- assertPositiveInteger("totalFrames", totalFrames);
3144
- assertPositiveInteger("segmentFrames", segmentFrames);
3145
- const slices = [];
3146
- for (let start = 0, index = 0; start < totalFrames; start += segmentFrames, index += 1) {
3147
- slices.push({
3148
- index,
3149
- startFrame: start,
3150
- endFrame: Math.min(totalFrames, start + segmentFrames)
3151
- });
3152
- }
3153
- return slices;
3154
- }
3155
- function resolveSegmentFrames(env) {
3156
- const raw = env.HF_SEGMENT_FRAMES;
3157
- if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_FRAMES;
3158
- const parsed = Number(raw);
3159
- if (!Number.isInteger(parsed)) return DEFAULT_SEGMENT_FRAMES;
3160
- return Math.max(MIN_SEGMENT_FRAMES, parsed);
3161
- }
3162
-
3163
- // src/services/render/segmentQueue.ts
3164
- function createSegmentQueue(slices) {
3165
- let cursor = 0;
3166
- return {
3167
- next: () => slices[cursor++],
3168
- remaining: () => Math.max(0, slices.length - cursor)
3169
- };
3170
- }
3171
-
3172
- // src/services/render/segmentRecycle.ts
3173
- var DEFAULT_SEGMENT_BROWSER_RECYCLE = 3;
3174
- function resolveSegmentBrowserRecycle(env) {
3175
- const raw = env.HF_SEGMENT_BROWSER_RECYCLE;
3176
- if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_BROWSER_RECYCLE;
3177
- const parsed = Number(raw);
3178
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SEGMENT_BROWSER_RECYCLE;
3179
- }
3180
- var TARGET_LOSS_PATTERNS = [/target closed/i, /detached frame/i, /session closed/i];
3181
- function isTargetLossError(error) {
3182
- if (!(error instanceof Error)) return false;
3183
- return TARGET_LOSS_PATTERNS.some((re) => re.test(error.message));
3184
- }
3185
-
3186
- // src/services/render/stages/captureStreamingStage.ts
3187
- import { mkdtemp, writeFile } from "node:fs/promises";
3188
- import { tmpdir } from "node:os";
3189
- import { join as join8 } from "node:path";
3190
- import {
3191
- DrawElementVerificationError,
3192
- captureFrameToBuffer,
3193
- captureFrameToBufferPipelined,
3194
- captureFramesBatchPipelined,
3195
- closeCaptureSession,
3196
- createCaptureSession,
3197
- createFrameReorderBuffer,
3198
- distributeFrames,
3199
- distributeFramesInterleaved,
3200
- executeParallelCapture,
3201
- getCapturePerfSummary,
3202
- psnrDb,
3203
- resolveDeVerifyMinDb,
3204
- recaptureDrawElementFrameForVerify,
3205
- completeDeferredDrawElementInit,
3206
- initializeSession,
3207
- prepareCaptureSessionForReuse,
3208
- spawnStreamingEncoder
3209
- } from "@hyperframes/engine";
3210
-
3211
- // src/services/render/perfSummary.ts
3212
- import { arch, cpus, platform, totalmem } from "node:os";
3213
- import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
3214
- function worstSubTimelineWaitOutcome(perfs) {
3215
- const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
3216
- if (outcomes.length === 0) return void 0;
3217
- if (outcomes.includes("script_failure")) return "script_failure";
3218
- if (outcomes.includes("timeout")) return "timeout";
3219
- return "ready";
3220
- }
3221
- function pushWorkerDedupPerfs(results, sink) {
3222
- for (const r of results) {
3223
- if (r.perf) sink.push(r.perf);
3224
- }
3225
- }
3226
- function roundDb(value) {
3227
- return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
3228
- }
3229
- function aggregateDrawElement(perfs, de) {
3230
- if (perfs.length === 0) return void 0;
3231
- const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
3232
- const gateReasons = [
3233
- ...new Set(
3234
- [de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
3235
- )
3236
- ].sort();
3237
- const gpuRenderers = [
3238
- ...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
3239
- ].sort();
3240
- const drain = de.drainStats;
3241
- return {
3242
- mode: modes.join("|") || "unknown",
3243
- compileGate: de.compileGate,
3244
- clampReason: de.clampReason,
3245
- workerInversion: de.workerInversion ?? "none",
3246
- preInversionWorkers: de.preInversionWorkers,
3247
- compositionElementCount: de.compositionElementCount,
3248
- compositionElementCountSource: de.compositionElementCountSource,
3249
- compositionElementTags: de.compositionElementTags,
3250
- arollVideoCount: de.arollVideoCount,
3251
- heygenVideoCount: de.heygenVideoCount,
3252
- adaptersUsed: de.adaptersUsed,
3253
- audioCount: de.audioCount,
3254
- imageCount: de.imageCount,
3255
- subCompositionCount: de.subCompositionCount,
3256
- audioGroupCount: de.audioGroupCount,
3257
- colorGradingCount: de.colorGradingCount,
3258
- hasLut: de.hasLut,
3259
- rootBodyMismatch: de.rootBodyMismatch,
3260
- rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
3261
- vfxHostCount: de.vfxHostCount,
3262
- vfxCapture: de.vfxCapture,
3263
- vfxTypes: de.vfxTypes,
3264
- shortBand: de.shortBand,
3265
- parallelRouter: de.parallelRouter ?? "none",
3266
- preRouterWorkers: de.preRouterWorkers,
3267
- gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
3268
- gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
3269
- workerEncode: perfs.some((p) => p.deWorkerEncode),
3270
- verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
3271
- verifyChecked: drain?.verifyChecked ?? 0,
3272
- verifyMinDb: roundDb(drain?.verifyMinDb),
3273
- verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
3274
- selfVerifyFallback: de.selfVerifyFallback,
3275
- fallbackReason: de.fallbackReason,
3276
- fallbackFailedDb: roundDb(de.fallbackFailedDb),
3277
- fallbackFrameIndex: de.fallbackFrameIndex,
3278
- fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
3279
- blankSuspects: drain?.blankSuspects ?? 0,
3280
- blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
3281
- blankRecaptures: drain?.blankRecaptures ?? 0,
3282
- boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
3283
- ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
3284
- frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
3285
- };
3286
- }
3287
- function maxDefined(values) {
3288
- const present = values.filter((v) => typeof v === "number");
3289
- return present.length > 0 ? Math.max(...present) : void 0;
3290
- }
3291
- function aggregateChromeMemory(perfs) {
3292
- const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
3293
- if (sampled.length === 0) return void 0;
3294
- return {
3295
- browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
3296
- rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
3297
- // Last samples are per session; summing approximates the whole fleet's
3298
- // footprint at the end of capture (workers run concurrently).
3299
- rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
3300
- gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
3301
- samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
3302
- };
3303
- }
3304
- function aggregateDedup(perfs) {
3305
- if (perfs.length === 0) return void 0;
3306
- const armed = perfs.some((p) => p.staticDedupArmed);
3307
- const skipReasons = armed ? [] : [
3308
- ...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
3309
- ].sort();
3310
- const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
3311
- const verificationOutcomes = [
3312
- ...new Set(
3313
- verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
3314
- )
3315
- ].sort();
3316
- return {
3317
- enabled: perfs.some((p) => p.staticDedupEnabled),
3318
- armed,
3319
- predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
3320
- reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
3321
- skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
3322
- ...verificationPerfs.length === 0 ? {} : {
3323
- verifiedFrames: verificationPerfs.reduce(
3324
- (sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
3325
- 0
3326
- ),
3327
- verificationOutcomes,
3328
- plannedRuns: verificationPerfs.reduce(
3329
- (sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
3330
- 0
3331
- ),
3332
- completedRuns: verificationPerfs.reduce(
3333
- (sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
3334
- 0
3335
- ),
3336
- screenshots: verificationPerfs.reduce(
3337
- (sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
3338
- 0
3339
- ),
3340
- seeks: verificationPerfs.reduce(
3341
- (sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
3342
- 0
3343
- ),
3344
- comparisons: verificationPerfs.reduce(
3345
- (sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
3346
- 0
3347
- ),
3348
- verificationElapsedMs: verificationPerfs.reduce(
3349
- (sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
3350
- 0
3351
- )
3339
+ const frameSource = hdrVideoFrameSources.get(videoId);
3340
+ if (frameSource) {
3341
+ cleanupHdrVideoFrameSource(frameSource, log);
3342
+ hdrVideoFrameSources.delete(videoId);
3343
+ }
3344
+ cleanedUpVideos.add(videoId);
3345
+ }
3352
3346
  }
3353
- };
3347
+ }
3354
3348
  }
3355
- function aggregateBeginFrameReuse(perfs) {
3356
- const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
3357
- const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
3358
- if (noDamageFrames + hasDamageFrames === 0) return void 0;
3359
- return { noDamageFrames, hasDamageFrames };
3349
+
3350
+ // src/services/render/segmentPlan.ts
3351
+ var DEFAULT_SEGMENT_FRAMES = 3e3;
3352
+ var MIN_SEGMENT_FRAMES = 30;
3353
+ function assertPositiveInteger(name, value) {
3354
+ if (!Number.isInteger(value) || value <= 0) {
3355
+ throw new Error(`[segmentPlan] ${name} must be a positive integer (got ${String(value)})`);
3356
+ }
3360
3357
  }
3361
- function buildRenderPerfSummary(input) {
3358
+ function planSegments(totalFrames, segmentFrames) {
3359
+ assertPositiveInteger("totalFrames", totalFrames);
3360
+ assertPositiveInteger("segmentFrames", segmentFrames);
3361
+ const slices = [];
3362
+ for (let start = 0, index = 0; start < totalFrames; start += segmentFrames, index += 1) {
3363
+ slices.push({
3364
+ index,
3365
+ startFrame: start,
3366
+ endFrame: Math.min(totalFrames, start + segmentFrames)
3367
+ });
3368
+ }
3369
+ return slices;
3370
+ }
3371
+ function resolveSegmentFrames(env) {
3372
+ const raw = env.HF_SEGMENT_FRAMES;
3373
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_FRAMES;
3374
+ const parsed = Number(raw);
3375
+ if (!Number.isInteger(parsed)) return DEFAULT_SEGMENT_FRAMES;
3376
+ return Math.max(MIN_SEGMENT_FRAMES, parsed);
3377
+ }
3378
+
3379
+ // src/services/render/segmentQueue.ts
3380
+ function createSegmentQueue(slices) {
3381
+ let cursor = 0;
3362
3382
  return {
3363
- renderId: input.job.id,
3364
- totalElapsedMs: input.totalElapsedMs,
3365
- // RenderPerfSummary surfaces fps as a decimal because it lands in JSON
3366
- // payloads (CLI telemetry, regression-harness reports) where a single
3367
- // number is friendlier than `{num,den}`. Callers needing the rational
3368
- // back can read `job.config.fps`.
3369
- fps: fpsToNumber4(input.job.config.fps),
3370
- quality: input.job.config.quality,
3371
- workers: input.workerCount,
3372
- workerSizing: input.workerSizing,
3373
- chunkedEncode: input.enableChunkedEncode,
3374
- chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
3375
- compositionDurationSeconds: input.compositionDurationSeconds,
3376
- totalFrames: input.totalFrames,
3377
- resolution: { width: input.outputWidth, height: input.outputHeight },
3378
- videoCount: input.videoCount,
3379
- audioCount: input.audioCount,
3380
- stages: input.perfStages,
3381
- videoExtractBreakdown: input.videoExtractBreakdown,
3382
- tmpPeakBytes: input.tmpPeakBytes,
3383
- captureCalibration: input.captureCalibration ? {
3384
- sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
3385
- p95Ms: input.captureCalibration.estimate.p95Ms,
3386
- multiplier: input.captureCalibration.estimate.multiplier,
3387
- reasons: input.captureCalibration.estimate.reasons
3388
- } : void 0,
3389
- captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
3390
- hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
3391
- hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
3392
- observability: input.observability,
3393
- captureAvgMs: input.totalFrames > 0 ? Math.round(
3394
- (input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
3395
- ) : void 0,
3396
- subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
3397
- captureP50Ms: (() => {
3398
- const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
3399
- if (withSamples.length === 0) return void 0;
3400
- return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
3401
- })(),
3402
- peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
3403
- peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
3404
- staticDedup: aggregateDedup(input.dedupPerfs),
3405
- chromeMemory: aggregateChromeMemory(input.dedupPerfs),
3406
- beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
3407
- drawElement: aggregateDrawElement(
3408
- input.dedupPerfs,
3409
- input.drawElement ?? { selfVerifyFallback: false }
3410
- ),
3411
- host: {
3412
- platform: platform(),
3413
- arch: arch(),
3414
- cpuCount: cpus().length,
3415
- totalMemMb: Math.round(totalmem() / (1024 * 1024)),
3416
- nodeVersion: process.version,
3417
- gpuDisabled: input.gpuDisabled
3418
- }
3383
+ next: () => slices[cursor++],
3384
+ remaining: () => Math.max(0, slices.length - cursor)
3419
3385
  };
3420
3386
  }
3421
3387
 
3388
+ // src/services/render/segmentRecycle.ts
3389
+ var DEFAULT_SEGMENT_BROWSER_RECYCLE = 3;
3390
+ function resolveSegmentBrowserRecycle(env) {
3391
+ const raw = env.HF_SEGMENT_BROWSER_RECYCLE;
3392
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_BROWSER_RECYCLE;
3393
+ const parsed = Number(raw);
3394
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SEGMENT_BROWSER_RECYCLE;
3395
+ }
3396
+ var TARGET_LOSS_PATTERNS = [/target closed/i, /detached frame/i, /session closed/i];
3397
+ function isTargetLossError(error) {
3398
+ if (!(error instanceof Error)) return false;
3399
+ return TARGET_LOSS_PATTERNS.some((re) => re.test(error.message));
3400
+ }
3401
+
3422
3402
  // src/services/render/stages/captureStreamingStage.ts
3403
+ import { mkdtemp, writeFile } from "node:fs/promises";
3404
+ import { tmpdir } from "node:os";
3405
+ import { join as join8 } from "node:path";
3406
+ import {
3407
+ DrawElementVerificationError,
3408
+ captureFrameToBuffer,
3409
+ captureFrameToBufferPipelined,
3410
+ captureFramesBatchPipelined,
3411
+ closeCaptureSession,
3412
+ createCaptureSession,
3413
+ createFrameReorderBuffer,
3414
+ distributeFrames,
3415
+ distributeFramesInterleaved,
3416
+ executeParallelCapture,
3417
+ getCapturePerfSummary,
3418
+ psnrDb,
3419
+ resolveDeVerifyMinDb,
3420
+ recaptureDrawElementFrameForVerify,
3421
+ completeDeferredDrawElementInit,
3422
+ initializeSession,
3423
+ prepareCaptureSessionForReuse,
3424
+ spawnStreamingEncoder
3425
+ } from "@hyperframes/engine";
3423
3426
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3424
3427
  var DE_STALL_POLL_MS = 5e3;
3425
3428
  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.";
3429
+ function poolFailure(error, probeSession) {
3430
+ return wrapCaptureStageError(error, probeSession?.browserConsoleBuffer ?? []);
3431
+ }
3426
3432
  var ParallelCaptureStallError = class extends Error {
3427
3433
  variant;
3428
3434
  constructor(args) {
@@ -3869,7 +3875,7 @@ async function runCaptureStreamingStage(input) {
3869
3875
  }
3870
3876
  );
3871
3877
  } catch (err) {
3872
- if (parallelDrainError) throw parallelDrainError;
3878
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
3873
3879
  if (stalled && abortSignal?.aborted !== true) {
3874
3880
  throw new ParallelCaptureStallError({
3875
3881
  variant: "pool",
@@ -3880,13 +3886,15 @@ async function runCaptureStreamingStage(input) {
3880
3886
  phaseSummary: phaseSummary()
3881
3887
  });
3882
3888
  }
3883
- throw err;
3889
+ throw poolFailure(err, probeSession);
3884
3890
  } finally {
3885
3891
  clearInterval(stallTimer);
3886
3892
  abortSignal?.removeEventListener("abort", forwardParentAbort);
3887
3893
  }
3888
- if (parallelDrainError) throw parallelDrainError;
3894
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
3889
3895
  pushWorkerDedupPerfs(workerResults, dedupPerfs);
3896
+ const workerPageErrors = [];
3897
+ pushWorkerPageErrors(workerResults, workerPageErrors);
3890
3898
  if (parallelGuardRan) {
3891
3899
  deDrainStats = parallelStats;
3892
3900
  }
@@ -3896,6 +3904,7 @@ async function runCaptureStreamingStage(input) {
3896
3904
  await closeCaptureSession(probeSession);
3897
3905
  probeSession = null;
3898
3906
  }
3907
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
3899
3908
  } else {
3900
3909
  const videoInjector = createRenderVideoFrameInjector();
3901
3910
  const session = probeSession ?? await createCaptureSession(
@@ -4342,8 +4351,9 @@ async function runCaptureSegmentedStage(input) {
4342
4351
 
4343
4352
  // src/services/render/segmentManifest.ts
4344
4353
  import { createHash as createHash2 } from "node:crypto";
4345
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4354
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4346
4355
  import { join as join10 } from "node:path";
4356
+ import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
4347
4357
  var MANIFEST_FILENAME = "segments.json";
4348
4358
  function computeSegmentPlanHash(input) {
4349
4359
  const ordered = [
@@ -4399,7 +4409,7 @@ function writeSegmentManifest(segmentDir, manifest) {
4399
4409
  mkdirSync4(segmentDir, { recursive: true });
4400
4410
  const tmp = join10(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
4401
4411
  writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
4402
- renameSync2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4412
+ renameWithRetry2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4403
4413
  }
4404
4414
  async function probeSegmentFrameCount(path, probe) {
4405
4415
  try {
@@ -5451,19 +5461,11 @@ import {
5451
5461
 
5452
5462
  // src/services/animatedGifPrep.ts
5453
5463
  import { createHash as createHash4 } from "node:crypto";
5454
- import {
5455
- copyFileSync as copyFileSync2,
5456
- existsSync as existsSync8,
5457
- mkdirSync as mkdirSync5,
5458
- readFileSync as readFileSync5,
5459
- renameSync as renameSync3,
5460
- rmSync as rmSync6,
5461
- statSync as statSync4
5462
- } from "node:fs";
5464
+ import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
5463
5465
  import { dirname as dirname5, isAbsolute as isAbsolute2, join as join12, resolve as resolve5 } from "node:path";
5464
5466
  import { parseHTML as parseHTML3 } from "linkedom";
5465
5467
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
5466
- import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2 } from "@hyperframes/engine";
5468
+ import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
5467
5469
  var PREPARED_GIF_SUBDIR = "_animated_gif";
5468
5470
  var CACHE_SCHEMA = "hfgif-v1";
5469
5471
  function splitUrlSuffix(src) {
@@ -5642,7 +5644,7 @@ async function ensurePreparedWebm(input) {
5642
5644
  throw new Error("Animated GIF transcode produced an empty output");
5643
5645
  }
5644
5646
  if (!isUsableFile(input.cachePath)) {
5645
- renameSync3(tmpPath, input.cachePath);
5647
+ renameWithRetry3(tmpPath, input.cachePath);
5646
5648
  } else {
5647
5649
  rmSync6(tmpPath, { force: true });
5648
5650
  }
@@ -8747,7 +8749,7 @@ import {
8747
8749
 
8748
8750
  // src/services/render/audioPadTrim.ts
8749
8751
  import { spawn } from "node:child_process";
8750
- import { mkdtempSync as mkdtempSync3, renameSync as renameSync4, rmSync as rmSync7 } from "node:fs";
8752
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync7 } from "node:fs";
8751
8753
  import { dirname as dirname7, join as join17 } from "node:path";
8752
8754
  import {
8753
8755
  DEFAULT_CONFIG,
@@ -8756,6 +8758,7 @@ import {
8756
8758
  extractAudioMetadata as extractAudioMetadata3,
8757
8759
  getFfprobeBinary,
8758
8760
  ManagedChildProcess,
8761
+ renameWithRetry as renameWithRetry4,
8759
8762
  runFfmpeg as runFfmpeg4,
8760
8763
  trackChildProcess
8761
8764
  } from "@hyperframes/engine";
@@ -9011,7 +9014,7 @@ async function enforceAacTruePeak(input) {
9011
9014
  }
9012
9015
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
9013
9016
  if (measuredPath !== correctedPath) return { success: true };
9014
- renameSync4(correctedPath, input.audioPath);
9017
+ renameWithRetry4(correctedPath, input.audioPath);
9015
9018
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
9016
9019
  }
9017
9020
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -9201,6 +9204,7 @@ async function runAudioStage(input) {
9201
9204
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
9202
9205
  let audioLoweredDb;
9203
9206
  if (result.hasAudio) {
9207
+ const guardStart = Date.now();
9204
9208
  const peak = await constrainAacTruePeak({
9205
9209
  audioPath: result.audioOutputPath,
9206
9210
  sourcePath: pcmSourcePath,
@@ -9210,6 +9214,7 @@ async function runAudioStage(input) {
9210
9214
  });
9211
9215
  if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
9212
9216
  audioLoweredDb = peak.loweredDb;
9217
+ input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
9213
9218
  }
9214
9219
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9215
9220
  } finally {
@@ -9235,6 +9240,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9235
9240
  let hasAudio = false;
9236
9241
  let audioError;
9237
9242
  let audioFailures;
9243
+ let audioPhaseMs;
9238
9244
  if (audios.length > 0) {
9239
9245
  let audioResult;
9240
9246
  try {
@@ -9284,6 +9290,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9284
9290
  assertAudioNotInterrupted(audioResult.failures);
9285
9291
  hasAudio = audioResult.success;
9286
9292
  audioFailures = audioResult.failures;
9293
+ audioPhaseMs = audioResult.phaseMs;
9287
9294
  if (!hasAudio) {
9288
9295
  audioError = audioResult.error ?? "audio mix failed for an unknown reason";
9289
9296
  for (const failure of audioResult.failures ?? []) {
@@ -9302,7 +9309,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9302
9309
  audioOutputPath,
9303
9310
  hasAudio,
9304
9311
  audioError,
9305
- audioFailures
9312
+ audioFailures,
9313
+ audioPhaseMs
9306
9314
  };
9307
9315
  }
9308
9316
 
@@ -9431,6 +9439,7 @@ async function runCaptureStage(input) {
9431
9439
  let { workerCount } = plan2;
9432
9440
  const { forceScreenshot } = plan2;
9433
9441
  let lastBrowserConsole = [];
9442
+ const workerPageErrors = [];
9434
9443
  let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
9435
9444
  const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
9436
9445
  if (frameRange !== void 0) {
@@ -9468,6 +9477,7 @@ async function runCaptureStage(input) {
9468
9477
  abortSignal,
9469
9478
  frameRangeStart: frameRange?.startFrame,
9470
9479
  dedupPerfs,
9480
+ pageErrorLines: workerPageErrors,
9471
9481
  onProgress: (progress) => {
9472
9482
  if (progress.latestWorkerPhase) {
9473
9483
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
@@ -9488,6 +9498,11 @@ async function runCaptureStage(input) {
9488
9498
  },
9489
9499
  cfg: captureCfg,
9490
9500
  log
9501
+ }).catch((error) => {
9502
+ throw wrapCaptureStageError(
9503
+ error,
9504
+ withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
9505
+ );
9491
9506
  });
9492
9507
  captureAttempts.push(...attempts);
9493
9508
  const lastAttempt = attempts[attempts.length - 1];
@@ -9500,6 +9515,7 @@ async function runCaptureStage(input) {
9500
9515
  await closeCaptureSession5(probeSession);
9501
9516
  probeSession = null;
9502
9517
  }
9518
+ lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
9503
9519
  } else {
9504
9520
  const sequential = await runSequentialDiskCapture(
9505
9521
  input,
@@ -10815,7 +10831,7 @@ function videoEncoderOptions(input, engineCfg) {
10815
10831
  };
10816
10832
  }
10817
10833
  function canEncodeWhileCapturing(input, workerCount, cpuCount) {
10818
- 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;
10834
+ 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";
10819
10835
  }
10820
10836
  async function readLandedFrame(workerPath, mergedPath) {
10821
10837
  if (workerPath !== void 0) {
@@ -11470,6 +11486,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11470
11486
  options.cfg
11471
11487
  );
11472
11488
  pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
11489
+ if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
11473
11490
  } finally {
11474
11491
  await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
11475
11492
  }
@@ -13868,7 +13885,6 @@ import {
13868
13885
  existsSync as existsSync21,
13869
13886
  linkSync,
13870
13887
  mkdirSync as mkdirSync13,
13871
- renameSync as renameSync6,
13872
13888
  rmSync as rmSync12,
13873
13889
  writeFileSync as writeFileSync8
13874
13890
  } from "node:fs";
@@ -13881,7 +13897,9 @@ import {
13881
13897
  getEncoderPreset as getEncoderPreset4,
13882
13898
  normalizeVp9CpuUsed,
13883
13899
  resolveConfig as resolveConfig3,
13884
- declareContentAddressedUrl
13900
+ declareContentAddressedUrl,
13901
+ renameWithRetry as renameWithRetry6,
13902
+ declareProbeFactsUrl
13885
13903
  } from "@hyperframes/engine";
13886
13904
 
13887
13905
  // src/services/distributed/planFrameCost.ts
@@ -14822,15 +14840,9 @@ import { join as join31 } from "node:path";
14822
14840
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
14823
14841
 
14824
14842
  // src/services/distributed/mp4Stitch.ts
14825
- import {
14826
- createReadStream as createReadStream4,
14827
- createWriteStream,
14828
- mkdirSync as mkdirSync11,
14829
- mkdtempSync as mkdtempSync4,
14830
- renameSync as renameSync5,
14831
- rmSync as rmSync11
14832
- } from "node:fs";
14843
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
14833
14844
  import { open } from "node:fs/promises";
14845
+ import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
14834
14846
  import { dirname as dirname12, join as join30 } from "node:path";
14835
14847
  import { finished, pipeline } from "node:stream/promises";
14836
14848
  function boxAt(src, off, at, limit) {
@@ -15203,7 +15215,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15203
15215
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
15204
15216
  out.end();
15205
15217
  await finished(out);
15206
- renameSync5(join30(workDir, "out.mp4"), outputPath);
15218
+ renameWithRetry5(join30(workDir, "out.mp4"), outputPath);
15207
15219
  } finally {
15208
15220
  out.destroy();
15209
15221
  rmSync11(workDir, { recursive: true, force: true });
@@ -15929,7 +15941,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
15929
15941
  const needsAlpha = outputNeedsAlpha(config.format);
15930
15942
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
15931
15943
  const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
15932
- for (const [, file] of remoteFiles) declareContentAddressedUrl(file.url, file.sha256);
15944
+ for (const [, file] of remoteFiles) {
15945
+ declareContentAddressedUrl(file.url, file.sha256);
15946
+ if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
15947
+ }
15933
15948
  const urlByAbsentLocalPath = new Map(
15934
15949
  remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
15935
15950
  );
@@ -16051,7 +16066,8 @@ function mixPlanAudio(c, compiledDir) {
16051
16066
  audios: c.composition.audios,
16052
16067
  masterFxChain: c.composition.masterFxChain,
16053
16068
  abortSignal: c.abortSignal,
16054
- assertNotAborted: c.assertNotAborted
16069
+ assertNotAborted: c.assertNotAborted,
16070
+ log: c.log
16055
16071
  });
16056
16072
  }
16057
16073
  async function extractPlanVideos(c, deferRangeExtraction) {
@@ -16070,6 +16086,7 @@ async function extractPlanVideos(c, deferRangeExtraction) {
16070
16086
  if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
16071
16087
  if (extractResult.extractionResult) {
16072
16088
  assertVideoExtractionSucceeded(extractResult.extractionResult);
16089
+ c.log.info("[plan] extract phases", { ...extractResult.extractionResult.phaseBreakdown });
16073
16090
  }
16074
16091
  return extractResult;
16075
16092
  }
@@ -16115,12 +16132,12 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16115
16132
  const videoSourcesDir = join33(planDir, "video-sources");
16116
16133
  rmSync12(videoSourcesDir, { recursive: true, force: true });
16117
16134
  if (existsSync21(stagedVideoFrames)) {
16118
- renameSync6(stagedVideoFrames, videoFramesDst);
16135
+ renameWithRetry6(stagedVideoFrames, videoFramesDst);
16119
16136
  } else {
16120
16137
  mkdirSync13(videoFramesDst, { recursive: true });
16121
16138
  }
16122
16139
  if (existsSync21(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
16123
- renameSync6(compiledDir, finalCompiledDir);
16140
+ renameWithRetry6(compiledDir, finalCompiledDir);
16124
16141
  const shipVideoSource = createVideoSourceShipper({
16125
16142
  planDir,
16126
16143
  projectDir,
@@ -16223,7 +16240,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16223
16240
  }
16224
16241
  const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
16225
16242
  if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
16226
- renameSync6(audioResult.audioOutputPath, planAudioPath);
16243
+ renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
16227
16244
  }
16228
16245
  if (chunksCarryAudio && existsSync21(planAudioPath)) {
16229
16246
  if (options.sliceChunkAudio) {
@@ -17740,7 +17757,6 @@ import {
17740
17757
  mkdtempSync as mkdtempSync7,
17741
17758
  readFileSync as readFileSync13,
17742
17759
  readdirSync as readdirSync9,
17743
- renameSync as renameSync8,
17744
17760
  rmSync as rmSync15,
17745
17761
  statSync as statSync10,
17746
17762
  writeFileSync as writeFileSync11
@@ -17748,7 +17764,7 @@ import {
17748
17764
  import { tmpdir as tmpdir4 } from "node:os";
17749
17765
  import { dirname as dirname16, isAbsolute as isAbsolute5, join as join39, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
17750
17766
  import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
17751
- import { createFrameLookupTable as createFrameLookupTable4 } from "@hyperframes/engine";
17767
+ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
17752
17768
 
17753
17769
  // src/services/distributed/planV2Publisher.ts
17754
17770
  import {
@@ -17757,11 +17773,11 @@ import {
17757
17773
  linkSync as linkSync2,
17758
17774
  mkdirSync as mkdirSync15,
17759
17775
  mkdtempSync as mkdtempSync6,
17760
- renameSync as renameSync7,
17761
17776
  rmSync as rmSync14,
17762
17777
  statSync as statSync9,
17763
17778
  writeFileSync as writeFileSync10
17764
17779
  } from "node:fs";
17780
+ import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
17765
17781
  import { dirname as dirname15, join as join38 } from "node:path";
17766
17782
 
17767
17783
  // src/services/distributed/planV2Errors.ts
@@ -17850,7 +17866,7 @@ var LocalPlanV2ArtifactPublisher = class {
17850
17866
  if (!canFallbackToCopy(error)) throw error;
17851
17867
  copyFileSync6(blob.sourcePath, temporaryPath);
17852
17868
  }
17853
- renameSync7(temporaryPath, destinationPath);
17869
+ renameWithRetry7(temporaryPath, destinationPath);
17854
17870
  } finally {
17855
17871
  rmSync14(stagingDir, { recursive: true, force: true });
17856
17872
  }
@@ -17864,7 +17880,7 @@ var LocalPlanV2ArtifactPublisher = class {
17864
17880
  }
17865
17881
  }
17866
17882
  writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
17867
- renameSync7(this.temporaryDir, this.destinationDir);
17883
+ renameWithRetry7(this.temporaryDir, this.destinationDir);
17868
17884
  this.#committed = true;
17869
17885
  }
17870
17886
  async abort() {
@@ -18131,7 +18147,7 @@ function writeBlob(sourcePath, destinationPath) {
18131
18147
  const temporaryPath = join39(temporaryDir, "blob");
18132
18148
  try {
18133
18149
  copyFileSync7(sourcePath, temporaryPath);
18134
- renameSync8(temporaryPath, destinationPath);
18150
+ renameWithRetry8(temporaryPath, destinationPath);
18135
18151
  } finally {
18136
18152
  rmSync15(temporaryDir, { recursive: true, force: true });
18137
18153
  }
@@ -18263,7 +18279,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18263
18279
  canonicalJsonStringify(publication.manifest),
18264
18280
  "utf-8"
18265
18281
  );
18266
- renameSync8(tempDir, planV2Dir);
18282
+ renameWithRetry8(tempDir, planV2Dir);
18267
18283
  return resultFromManifest(planV2Dir, publication.manifest);
18268
18284
  } catch (error) {
18269
18285
  rmSync15(tempDir, { recursive: true, force: true });
@@ -18548,7 +18564,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18548
18564
  }),
18549
18565
  "utf-8"
18550
18566
  );
18551
- renameSync8(tempDir, destinationDir);
18567
+ renameWithRetry8(tempDir, destinationDir);
18552
18568
  } catch (error) {
18553
18569
  rmSync15(tempDir, { recursive: true, force: true });
18554
18570
  throw error;
@@ -18626,7 +18642,6 @@ import {
18626
18642
  mkdtempSync as mkdtempSync8,
18627
18643
  readFileSync as readFileSync14,
18628
18644
  readdirSync as readdirSync10,
18629
- renameSync as renameSync9,
18630
18645
  rmSync as rmSync16,
18631
18646
  statSync as statSync11,
18632
18647
  writeFileSync as writeFileSync12
@@ -18640,7 +18655,8 @@ import {
18640
18655
  muxVideoWithAudio as muxVideoWithAudio2,
18641
18656
  resolveConfig as resolveConfig6,
18642
18657
  runFfmpeg as runFfmpeg7,
18643
- appendSoftwareEncoderThreadsArg
18658
+ appendSoftwareEncoderThreadsArg,
18659
+ renameWithRetry as renameWithRetry9
18644
18660
  } from "@hyperframes/engine";
18645
18661
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
18646
18662
  function cfrRateControlArgs(locked) {
@@ -18697,7 +18713,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18697
18713
  const finalPath = join40(workDir, `output.${format}`);
18698
18714
  const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
18699
18715
  await writeJoined(ctx, videoSources, audio, finalPath);
18700
- renameSync9(finalPath, outputPath);
18716
+ renameWithRetry9(finalPath, outputPath);
18701
18717
  log.info("[assemble] stage timings", {
18702
18718
  ...stageMs,
18703
18719
  chunks: chunkPaths.length,
@@ -18949,6 +18965,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
18949
18965
  // src/services/distributed/renderChunk.ts
18950
18966
  import { randomBytes } from "node:crypto";
18951
18967
  import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
18968
+ import { cpus as cpus3 } from "node:os";
18952
18969
  import { join as join41 } from "node:path";
18953
18970
  import {
18954
18971
  assertSwiftShader,
@@ -19253,6 +19270,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19253
19270
  let forceScreenshotForChunk = encoder.forceScreenshot;
19254
19271
  let shownBoxes;
19255
19272
  let extraction;
19273
+ const overlap = { current: null };
19256
19274
  try {
19257
19275
  if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
19258
19276
  const bootStarted = Date.now();
@@ -19340,6 +19358,59 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19340
19358
  videoFrameLookup,
19341
19359
  frameCacheOptions(cfg, chunkWorkerCount)
19342
19360
  );
19361
+ const isPngSequence = plan2.dimensions.format === "png-sequence";
19362
+ outputKind = isPngSequence ? "frame-dir" : "file";
19363
+ const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
19364
+ const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
19365
+ const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
19366
+ const effectiveQuality = encoder.crf ?? preset.quality;
19367
+ const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
19368
+ const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
19369
+ if (isPngSequence) {
19370
+ if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
19371
+ } else {
19372
+ const outDir = join41(outputChunkPath, "..");
19373
+ if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
19374
+ }
19375
+ const encodeInput = {
19376
+ job,
19377
+ log,
19378
+ outputPath: videoOnlyPath,
19379
+ framesDir,
19380
+ videoOnlyPath,
19381
+ width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
19382
+ height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
19383
+ needsAlpha: plan2.dimensions.format !== "mp4",
19384
+ captureImageFormat: captureOptions.format ?? "jpeg",
19385
+ // The encoder writes video only; audio joins at assemble time or in the
19386
+ // chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
19387
+ // sidecar AND the mp4 audio mux.
19388
+ hasAudio: false,
19389
+ isPngSequence,
19390
+ // `DistributedFormat` has no "gif" member — distributed chunks are
19391
+ // always video segments (gif renders in-process only).
19392
+ isGif: false,
19393
+ preset,
19394
+ effectiveQuality,
19395
+ effectiveBitrate,
19396
+ engineConfig: {
19397
+ ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
19398
+ vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
19399
+ },
19400
+ // Distributed chunks emit a single ffmpeg call per chunk; the
19401
+ // in-process per-chunk-within-chunk path would re-split our
19402
+ // already-chunked work.
19403
+ enableChunkedEncode: false,
19404
+ chunkedEncodeSize: framesInChunk,
19405
+ abortSignal: void 0,
19406
+ assertNotAborted: () => {
19407
+ },
19408
+ // GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
19409
+ // first frame is an IDR keyframe and concat-copy at assemble time
19410
+ // round-trips losslessly.
19411
+ lockGopForChunkConcat: !isPngSequence,
19412
+ gopSize: framesInChunk
19413
+ };
19343
19414
  const captureStarted = Date.now();
19344
19415
  captureMode = await runCaptureWithScreenshotFallback({
19345
19416
  forceScreenshot: forceScreenshotForChunk,
@@ -19372,6 +19443,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19372
19443
  }
19373
19444
  const captureSession = session;
19374
19445
  session = null;
19446
+ overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus3().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
19375
19447
  await runCaptureStage({
19376
19448
  fileServer,
19377
19449
  workDir,
@@ -19391,7 +19463,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19391
19463
  abortSignal: void 0,
19392
19464
  assertNotAborted: () => {
19393
19465
  },
19394
- frameRange: range
19466
+ frameRange: range,
19467
+ onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame)
19468
+ }).catch(async (captureError) => {
19469
+ await overlap.current?.abort();
19470
+ overlap.current = null;
19471
+ throw captureError;
19395
19472
  });
19396
19473
  return observedChunkCaptureMode(capturePerfs);
19397
19474
  },
@@ -19423,60 +19500,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19423
19500
  log
19424
19501
  );
19425
19502
  framesEncoded = framesInChunk;
19426
- const isPngSequence = plan2.dimensions.format === "png-sequence";
19427
- outputKind = isPngSequence ? "frame-dir" : "file";
19428
- const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
19429
- const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
19430
- const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
19431
- const effectiveQuality = encoder.crf ?? preset.quality;
19432
- const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
19433
- const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
19434
- if (isPngSequence) {
19435
- if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
19436
- } else {
19437
- const outDir = join41(outputChunkPath, "..");
19438
- if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
19439
- }
19440
19503
  const encodeStarted = Date.now();
19441
- await runEncodeStage({
19442
- job,
19443
- log,
19444
- outputPath: videoOnlyPath,
19445
- framesDir,
19446
- videoOnlyPath,
19447
- width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
19448
- height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
19449
- needsAlpha: plan2.dimensions.format !== "mp4",
19450
- captureImageFormat: captureOptions.format ?? "jpeg",
19451
- // The encoder writes video only; audio joins at assemble time or in the
19452
- // chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
19453
- // sidecar AND the mp4 audio mux.
19454
- hasAudio: false,
19455
- isPngSequence,
19456
- // `DistributedFormat` has no "gif" member — distributed chunks are
19457
- // always video segments (gif renders in-process only).
19458
- isGif: false,
19459
- preset,
19460
- effectiveQuality,
19461
- effectiveBitrate,
19462
- engineConfig: {
19463
- ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
19464
- vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
19465
- },
19466
- // Distributed chunks emit a single ffmpeg call per chunk; the
19467
- // in-process per-chunk-within-chunk path would re-split our
19468
- // already-chunked work.
19469
- enableChunkedEncode: false,
19470
- chunkedEncodeSize: framesInChunk,
19471
- abortSignal: void 0,
19472
- assertNotAborted: () => {
19473
- },
19474
- // GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
19475
- // first frame is an IDR keyframe and concat-copy at assemble time
19476
- // round-trips losslessly.
19477
- lockGopForChunkConcat: !isPngSequence,
19478
- gopSize: framesInChunk
19479
- });
19504
+ const overlapped = overlap.current;
19505
+ overlap.current = null;
19506
+ await (overlapped ? overlapped.finish() : runEncodeStage(encodeInput));
19480
19507
  if (encoder.chunksCarryAudio) {
19481
19508
  const audioScratchPath = `${outputChunkPath}.audio.m4a`;
19482
19509
  const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
@@ -19494,6 +19521,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19494
19521
  }
19495
19522
  encodeStageMs = Date.now() - encodeStarted;
19496
19523
  } finally {
19524
+ await overlap.current?.abort().catch(() => {
19525
+ });
19497
19526
  await extraction?.catch(() => {
19498
19527
  });
19499
19528
  if (session) {
@@ -19602,6 +19631,9 @@ var READ_CHUNK_BYTES = 256 * 1024;
19602
19631
  // src/services/distributed/audioSidecar.ts
19603
19632
  import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
19604
19633
  var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
19634
+
19635
+ // src/services/distributed/probeFacts.ts
19636
+ import { buildProbeFactsText } from "@hyperframes/engine";
19605
19637
  export {
19606
19638
  CURRENT_PLAN_PROTOCOL,
19607
19639
  DEFAULT_CONFIG2 as DEFAULT_CONFIG,