@hyperframes/producer 0.8.192 → 0.8.194
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/distributed.d.ts +1 -0
- package/dist/distributed.js +321 -122
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +71 -65
- package/dist/index.js +580 -456
- package/dist/public-server.js +435 -323
- package/dist/services/distributed/plan.d.ts +2 -0
- package/dist/services/distributed/probeFacts.d.ts +4 -0
- package/dist/services/htmlCompiler.d.ts +2 -1
- package/dist/services/render/artifactTransaction.d.ts +3 -1
- package/dist/services/render/perfSummary.d.ts +6 -0
- package/dist/services/render/stages/audioStage.d.ts +3 -1
- package/dist/services/renderOrchestrator.d.ts +6 -2
- package/package.json +7 -7
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) {
|
|
@@ -1573,8 +1564,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
|
|
|
1573
1564
|
`Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
|
|
1574
1565
|
);
|
|
1575
1566
|
}
|
|
1567
|
+
function hasUsableFrameCount(frames) {
|
|
1568
|
+
return frames !== void 0 && Number.isFinite(frames) && frames > 0;
|
|
1569
|
+
}
|
|
1576
1570
|
function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
|
|
1577
|
-
if (
|
|
1571
|
+
if (!hasUsableFrameCount(probedFrames)) {
|
|
1578
1572
|
return;
|
|
1579
1573
|
}
|
|
1580
1574
|
const shortfall = expectedFrames - probedFrames;
|
|
@@ -1594,9 +1588,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
|
|
|
1594
1588
|
);
|
|
1595
1589
|
}
|
|
1596
1590
|
var ArtifactTransaction = class {
|
|
1597
|
-
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1591
|
+
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
|
|
1598
1592
|
this.kind = kind;
|
|
1599
1593
|
this.fileSystem = fileSystem;
|
|
1594
|
+
this.logger = logger;
|
|
1600
1595
|
this.destinationPath = resolve4(destinationPath);
|
|
1601
1596
|
this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
|
|
1602
1597
|
this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
|
|
@@ -1679,6 +1674,19 @@ var ArtifactTransaction = class {
|
|
|
1679
1674
|
probePath
|
|
1680
1675
|
);
|
|
1681
1676
|
if (expected.expectedFrames !== void 0) {
|
|
1677
|
+
if (!hasUsableFrameCount(probed.frames)) {
|
|
1678
|
+
try {
|
|
1679
|
+
this.logger.debug(
|
|
1680
|
+
"[Render] Frame-count validation skipped: no usable frame count was reported.",
|
|
1681
|
+
{
|
|
1682
|
+
event: "artifact_frame_count_unavailable",
|
|
1683
|
+
artifact: this.destinationPath,
|
|
1684
|
+
expectedFrames: expected.expectedFrames
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
} catch {
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1682
1690
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1683
1691
|
}
|
|
1684
1692
|
assertSizeMatches(expected, probed, probePath);
|
|
@@ -1894,58 +1902,10 @@ import {
|
|
|
1894
1902
|
|
|
1895
1903
|
// src/services/render/captureStageError.ts
|
|
1896
1904
|
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
1905
|
|
|
1933
|
-
// src/services/
|
|
1934
|
-
import {
|
|
1935
|
-
import {
|
|
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";
|
|
1906
|
+
// src/services/render/perfSummary.ts
|
|
1907
|
+
import { arch, cpus, platform, totalmem } from "node:os";
|
|
1908
|
+
import { fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
|
|
1949
1909
|
|
|
1950
1910
|
// src/services/render/hdrPerf.ts
|
|
1951
1911
|
function createHdrPerfCollector() {
|
|
@@ -2051,7 +2011,280 @@ function finalizeHdrPerf(perf) {
|
|
|
2051
2011
|
};
|
|
2052
2012
|
}
|
|
2053
2013
|
|
|
2014
|
+
// src/services/render/perfSummary.ts
|
|
2015
|
+
function worstSubTimelineWaitOutcome(perfs) {
|
|
2016
|
+
const outcomes = perfs.map((p) => p.subTimelineWaitOutcome).filter((o) => !!o);
|
|
2017
|
+
if (outcomes.length === 0) return void 0;
|
|
2018
|
+
if (outcomes.includes("script_failure")) return "script_failure";
|
|
2019
|
+
if (outcomes.includes("timeout")) return "timeout";
|
|
2020
|
+
return "ready";
|
|
2021
|
+
}
|
|
2022
|
+
function pushWorkerDedupPerfs(results, sink) {
|
|
2023
|
+
for (const r of results) {
|
|
2024
|
+
if (r.perf) sink.push(r.perf);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
function pushWorkerPageErrors(results, sink) {
|
|
2028
|
+
for (const result of results) {
|
|
2029
|
+
for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
function withWorkerPageErrors(consoleLines, workerLines) {
|
|
2033
|
+
return [...consoleLines, ...workerLines.filter((line) => !consoleLines.includes(line))];
|
|
2034
|
+
}
|
|
2035
|
+
function roundDb(value) {
|
|
2036
|
+
return value === void 0 ? void 0 : Math.round(Math.min(value, 999) * 10) / 10;
|
|
2037
|
+
}
|
|
2038
|
+
function aggregateDrawElement(perfs, de) {
|
|
2039
|
+
if (perfs.length === 0) return void 0;
|
|
2040
|
+
const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
|
|
2041
|
+
const gateReasons = [
|
|
2042
|
+
...new Set(
|
|
2043
|
+
[de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
|
|
2044
|
+
)
|
|
2045
|
+
].sort();
|
|
2046
|
+
const gpuRenderers = [
|
|
2047
|
+
...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
|
|
2048
|
+
].sort();
|
|
2049
|
+
const drain = de.drainStats;
|
|
2050
|
+
return {
|
|
2051
|
+
mode: modes.join("|") || "unknown",
|
|
2052
|
+
compileGate: de.compileGate,
|
|
2053
|
+
clampReason: de.clampReason,
|
|
2054
|
+
workerInversion: de.workerInversion ?? "none",
|
|
2055
|
+
preInversionWorkers: de.preInversionWorkers,
|
|
2056
|
+
compositionElementCount: de.compositionElementCount,
|
|
2057
|
+
compositionElementCountSource: de.compositionElementCountSource,
|
|
2058
|
+
compositionElementTags: de.compositionElementTags,
|
|
2059
|
+
arollVideoCount: de.arollVideoCount,
|
|
2060
|
+
heygenVideoCount: de.heygenVideoCount,
|
|
2061
|
+
adaptersUsed: de.adaptersUsed,
|
|
2062
|
+
audioCount: de.audioCount,
|
|
2063
|
+
imageCount: de.imageCount,
|
|
2064
|
+
subCompositionCount: de.subCompositionCount,
|
|
2065
|
+
audioGroupCount: de.audioGroupCount,
|
|
2066
|
+
colorGradingCount: de.colorGradingCount,
|
|
2067
|
+
hasLut: de.hasLut,
|
|
2068
|
+
rootBodyMismatch: de.rootBodyMismatch,
|
|
2069
|
+
rootBodyDeltaPxBucket: de.rootBodyDeltaPxBucket,
|
|
2070
|
+
vfxHostCount: de.vfxHostCount,
|
|
2071
|
+
vfxCapture: de.vfxCapture,
|
|
2072
|
+
vfxTypes: de.vfxTypes,
|
|
2073
|
+
shortBand: de.shortBand,
|
|
2074
|
+
parallelRouter: de.parallelRouter ?? "none",
|
|
2075
|
+
preRouterWorkers: de.preRouterWorkers,
|
|
2076
|
+
gateReason: gateReasons.length > 0 ? gateReasons.join("|") : void 0,
|
|
2077
|
+
gpuRenderer: gpuRenderers.length > 0 ? gpuRenderers.join("|") : void 0,
|
|
2078
|
+
workerEncode: perfs.some((p) => p.deWorkerEncode),
|
|
2079
|
+
verifyArmed: perfs.reduce((sum2, p) => sum2 + (p.deVerifyArmed ?? 0), 0),
|
|
2080
|
+
verifyChecked: drain?.verifyChecked ?? 0,
|
|
2081
|
+
verifyMinDb: roundDb(drain?.verifyMinDb),
|
|
2082
|
+
verifyInitMs: perfs.reduce((sum2, p) => sum2 + (p.deVerifyInitMs ?? 0), 0),
|
|
2083
|
+
selfVerifyFallback: de.selfVerifyFallback,
|
|
2084
|
+
fallbackReason: de.fallbackReason,
|
|
2085
|
+
fallbackFailedDb: roundDb(de.fallbackFailedDb),
|
|
2086
|
+
fallbackFrameIndex: de.fallbackFrameIndex,
|
|
2087
|
+
fallbackThresholdDb: roundDb(de.fallbackThresholdDb),
|
|
2088
|
+
blankSuspects: drain?.blankSuspects ?? 0,
|
|
2089
|
+
blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
|
|
2090
|
+
blankRecaptures: drain?.blankRecaptures ?? 0,
|
|
2091
|
+
boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
|
|
2092
|
+
ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
|
|
2093
|
+
frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
function maxDefined(values) {
|
|
2097
|
+
const present = values.filter((v) => typeof v === "number");
|
|
2098
|
+
return present.length > 0 ? Math.max(...present) : void 0;
|
|
2099
|
+
}
|
|
2100
|
+
function aggregateChromeMemory(perfs) {
|
|
2101
|
+
const sampled = perfs.filter((p) => (p.chromeMemorySamples ?? 0) > 0);
|
|
2102
|
+
if (sampled.length === 0) return void 0;
|
|
2103
|
+
return {
|
|
2104
|
+
browserRssPeakMb: maxDefined(sampled.map((p) => p.chromeBrowserRssPeakMb)),
|
|
2105
|
+
rendererRssPeakMb: maxDefined(sampled.map((p) => p.chromeRendererRssPeakMb)),
|
|
2106
|
+
// Last samples are per session; summing approximates the whole fleet's
|
|
2107
|
+
// footprint at the end of capture (workers run concurrently).
|
|
2108
|
+
rssLastMb: sampled.reduce((sum2, p) => sum2 + (p.chromeRssLastMb ?? 0), 0),
|
|
2109
|
+
gpuProcessSeenLastSample: sampled.some((p) => p.chromeGpuProcessSeenLastSample === true),
|
|
2110
|
+
samples: sampled.reduce((sum2, p) => sum2 + (p.chromeMemorySamples ?? 0), 0)
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
function aggregateDedup(perfs) {
|
|
2114
|
+
if (perfs.length === 0) return void 0;
|
|
2115
|
+
const armed = perfs.some((p) => p.staticDedupArmed);
|
|
2116
|
+
const skipReasons = armed ? [] : [
|
|
2117
|
+
...new Set(perfs.map((p) => p.staticDedupSkipReason).filter((r) => !!r))
|
|
2118
|
+
].sort();
|
|
2119
|
+
const verificationPerfs = perfs.filter((perf) => perf.staticDedupVerificationOutcome);
|
|
2120
|
+
const verificationOutcomes = [
|
|
2121
|
+
...new Set(
|
|
2122
|
+
verificationPerfs.map((perf) => perf.staticDedupVerificationOutcome).filter((outcome) => outcome != null)
|
|
2123
|
+
)
|
|
2124
|
+
].sort();
|
|
2125
|
+
return {
|
|
2126
|
+
enabled: perfs.some((p) => p.staticDedupEnabled),
|
|
2127
|
+
armed,
|
|
2128
|
+
predictedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupPredicted ?? 0), 0),
|
|
2129
|
+
reusedFrames: perfs.reduce((sum2, p) => sum2 + (p.staticDedupReused ?? 0), 0),
|
|
2130
|
+
skipReason: skipReasons.length > 0 ? skipReasons.join("|") : void 0,
|
|
2131
|
+
...verificationPerfs.length === 0 ? {} : {
|
|
2132
|
+
verifiedFrames: verificationPerfs.reduce(
|
|
2133
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerified ?? 0),
|
|
2134
|
+
0
|
|
2135
|
+
),
|
|
2136
|
+
verificationOutcomes,
|
|
2137
|
+
plannedRuns: verificationPerfs.reduce(
|
|
2138
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationPlannedRuns ?? 0),
|
|
2139
|
+
0
|
|
2140
|
+
),
|
|
2141
|
+
completedRuns: verificationPerfs.reduce(
|
|
2142
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationCompletedRuns ?? 0),
|
|
2143
|
+
0
|
|
2144
|
+
),
|
|
2145
|
+
screenshots: verificationPerfs.reduce(
|
|
2146
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationScreenshots ?? 0),
|
|
2147
|
+
0
|
|
2148
|
+
),
|
|
2149
|
+
seeks: verificationPerfs.reduce(
|
|
2150
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationSeeks ?? 0),
|
|
2151
|
+
0
|
|
2152
|
+
),
|
|
2153
|
+
comparisons: verificationPerfs.reduce(
|
|
2154
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationComparisons ?? 0),
|
|
2155
|
+
0
|
|
2156
|
+
),
|
|
2157
|
+
verificationElapsedMs: verificationPerfs.reduce(
|
|
2158
|
+
(sum2, perf) => sum2 + (perf.staticDedupVerificationElapsedMs ?? 0),
|
|
2159
|
+
0
|
|
2160
|
+
)
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
function aggregateBeginFrameReuse(perfs) {
|
|
2165
|
+
const noDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameNoDamage ?? 0), 0);
|
|
2166
|
+
const hasDamageFrames = perfs.reduce((sum2, p) => sum2 + (p.beginFrameHasDamage ?? 0), 0);
|
|
2167
|
+
if (noDamageFrames + hasDamageFrames === 0) return void 0;
|
|
2168
|
+
return { noDamageFrames, hasDamageFrames };
|
|
2169
|
+
}
|
|
2170
|
+
function buildRenderPerfSummary(input) {
|
|
2171
|
+
return {
|
|
2172
|
+
renderId: input.job.id,
|
|
2173
|
+
totalElapsedMs: input.totalElapsedMs,
|
|
2174
|
+
// RenderPerfSummary surfaces fps as a decimal because it lands in JSON
|
|
2175
|
+
// payloads (CLI telemetry, regression-harness reports) where a single
|
|
2176
|
+
// number is friendlier than `{num,den}`. Callers needing the rational
|
|
2177
|
+
// back can read `job.config.fps`.
|
|
2178
|
+
fps: fpsToNumber3(input.job.config.fps),
|
|
2179
|
+
quality: input.job.config.quality,
|
|
2180
|
+
workers: input.workerCount,
|
|
2181
|
+
workerSizing: input.workerSizing,
|
|
2182
|
+
chunkedEncode: input.enableChunkedEncode,
|
|
2183
|
+
chunkSizeFrames: input.enableChunkedEncode ? input.chunkedEncodeSize : null,
|
|
2184
|
+
compositionDurationSeconds: input.compositionDurationSeconds,
|
|
2185
|
+
totalFrames: input.totalFrames,
|
|
2186
|
+
resolution: { width: input.outputWidth, height: input.outputHeight },
|
|
2187
|
+
videoCount: input.videoCount,
|
|
2188
|
+
audioCount: input.audioCount,
|
|
2189
|
+
stages: input.perfStages,
|
|
2190
|
+
videoExtractBreakdown: input.videoExtractBreakdown,
|
|
2191
|
+
tmpPeakBytes: input.tmpPeakBytes,
|
|
2192
|
+
captureCalibration: input.captureCalibration ? {
|
|
2193
|
+
sampledFrames: input.captureCalibration.samples.map((sample) => sample.frameIndex),
|
|
2194
|
+
p95Ms: input.captureCalibration.estimate.p95Ms,
|
|
2195
|
+
multiplier: input.captureCalibration.estimate.multiplier,
|
|
2196
|
+
reasons: input.captureCalibration.estimate.reasons
|
|
2197
|
+
} : void 0,
|
|
2198
|
+
captureAttempts: input.captureAttempts.length > 0 ? input.captureAttempts : void 0,
|
|
2199
|
+
hdrDiagnostics: input.hdrDiagnostics.videoExtractionFailures > 0 || input.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input.hdrDiagnostics } : void 0,
|
|
2200
|
+
hdrPerf: input.hdrPerf ? finalizeHdrPerf(input.hdrPerf) : void 0,
|
|
2201
|
+
observability: input.observability,
|
|
2202
|
+
captureAvgMs: input.totalFrames > 0 ? Math.round(
|
|
2203
|
+
(input.perfStages.captureFrameMs ?? input.perfStages.captureMs ?? 0) / input.totalFrames
|
|
2204
|
+
) : void 0,
|
|
2205
|
+
subTimelineWait: worstSubTimelineWaitOutcome(input.dedupPerfs),
|
|
2206
|
+
captureP50Ms: (() => {
|
|
2207
|
+
const withSamples = input.dedupPerfs.filter((p) => (p.p50TotalMs ?? 0) > 0);
|
|
2208
|
+
if (withSamples.length === 0) return void 0;
|
|
2209
|
+
return withSamples.reduce((a, b) => b.frames > a.frames ? b : a).p50TotalMs;
|
|
2210
|
+
})(),
|
|
2211
|
+
peakRssMb: Math.round(input.peakRssBytes / (1024 * 1024)),
|
|
2212
|
+
peakHeapUsedMb: Math.round(input.peakHeapUsedBytes / (1024 * 1024)),
|
|
2213
|
+
staticDedup: aggregateDedup(input.dedupPerfs),
|
|
2214
|
+
chromeMemory: aggregateChromeMemory(input.dedupPerfs),
|
|
2215
|
+
beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
|
|
2216
|
+
drawElement: aggregateDrawElement(
|
|
2217
|
+
input.dedupPerfs,
|
|
2218
|
+
input.drawElement ?? { selfVerifyFallback: false }
|
|
2219
|
+
),
|
|
2220
|
+
host: {
|
|
2221
|
+
platform: platform(),
|
|
2222
|
+
arch: arch(),
|
|
2223
|
+
cpuCount: cpus().length,
|
|
2224
|
+
totalMemMb: Math.round(totalmem() / (1024 * 1024)),
|
|
2225
|
+
nodeVersion: process.version,
|
|
2226
|
+
gpuDisabled: input.gpuDisabled
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/services/render/captureStageError.ts
|
|
2232
|
+
var CaptureStageError = class extends CaptureFailure {
|
|
2233
|
+
browserConsole;
|
|
2234
|
+
constructor(input) {
|
|
2235
|
+
const classified = classifyCaptureFailure(input.cause);
|
|
2236
|
+
super({
|
|
2237
|
+
kind: classified.kind,
|
|
2238
|
+
message: normalizeErrorMessage(input.cause),
|
|
2239
|
+
cause: input.cause,
|
|
2240
|
+
workerDiagnostics: classified.workerDiagnostics,
|
|
2241
|
+
pageErrorLines: classified.pageErrorLines
|
|
2242
|
+
});
|
|
2243
|
+
this.name = "CaptureStageError";
|
|
2244
|
+
this.browserConsole = withWorkerPageErrors(input.browserConsole, [
|
|
2245
|
+
...classified.pageErrorLines
|
|
2246
|
+
]);
|
|
2247
|
+
if (input.cause instanceof Error && input.cause.stack) {
|
|
2248
|
+
this.stack = input.cause.stack;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
function wrapCaptureStageError(error, browserConsole) {
|
|
2253
|
+
if (error instanceof CaptureStageError || error instanceof EncoderInterruptedError) return error;
|
|
2254
|
+
return new CaptureStageError({ cause: error, browserConsole });
|
|
2255
|
+
}
|
|
2256
|
+
function getCaptureStageBrowserConsole(error) {
|
|
2257
|
+
if (error instanceof CaptureStageError) return error.browserConsole;
|
|
2258
|
+
if (error instanceof CaptureFailure) return [...error.pageErrorLines];
|
|
2259
|
+
return [];
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// src/services/render/stages/captureHdrFrameShared.ts
|
|
2263
|
+
import {
|
|
2264
|
+
applyDomLayerMask as applyDomLayerMask2,
|
|
2265
|
+
blitRgba8OverRgb48le as blitRgba8OverRgb48le2,
|
|
2266
|
+
captureAlphaPng as captureAlphaPng2,
|
|
2267
|
+
decodePng as decodePng2,
|
|
2268
|
+
queryElementStacking as queryElementStacking2,
|
|
2269
|
+
removeDomLayerMask as removeDomLayerMask2
|
|
2270
|
+
} from "@hyperframes/engine";
|
|
2271
|
+
|
|
2054
2272
|
// src/services/hdrCompositor.ts
|
|
2273
|
+
import { readSync as readSync2, closeSync as closeSync3 } from "fs";
|
|
2274
|
+
import { join as join6 } from "path";
|
|
2275
|
+
import {
|
|
2276
|
+
captureAlphaPng,
|
|
2277
|
+
applyDomLayerMask,
|
|
2278
|
+
removeDomLayerMask,
|
|
2279
|
+
decodePng,
|
|
2280
|
+
blitRgba8OverRgb48le,
|
|
2281
|
+
blitRgb48leRegion,
|
|
2282
|
+
groupIntoLayers,
|
|
2283
|
+
blitRgb48leAffine,
|
|
2284
|
+
parseTransformMatrix,
|
|
2285
|
+
convertTransfer,
|
|
2286
|
+
isVideoHiddenBeforeStreamStart
|
|
2287
|
+
} from "@hyperframes/engine";
|
|
2055
2288
|
function countNonZeroAlpha(rgba) {
|
|
2056
2289
|
let n = 0;
|
|
2057
2290
|
for (let p = 3; p < rgba.length; p += 4) {
|
|
@@ -2518,7 +2751,7 @@ import {
|
|
|
2518
2751
|
resolveVideoExtractionWindow,
|
|
2519
2752
|
runFfmpeg
|
|
2520
2753
|
} from "@hyperframes/engine";
|
|
2521
|
-
import { fpsToFfmpegArg, fpsToNumber as
|
|
2754
|
+
import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
2522
2755
|
var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
|
|
2523
2756
|
function tempDirSafePrefix(id) {
|
|
2524
2757
|
const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
|
|
@@ -2754,7 +2987,7 @@ async function extractHdrVideoFrames(args) {
|
|
|
2754
2987
|
const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
|
|
2755
2988
|
framesDir,
|
|
2756
2989
|
plannedVideos,
|
|
2757
|
-
|
|
2990
|
+
fpsToNumber4(job.config.fps),
|
|
2758
2991
|
log
|
|
2759
2992
|
);
|
|
2760
2993
|
const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
|
|
@@ -3147,282 +3380,72 @@ function planSegments(totalFrames, segmentFrames) {
|
|
|
3147
3380
|
slices.push({
|
|
3148
3381
|
index,
|
|
3149
3382
|
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
|
-
)
|
|
3352
|
-
}
|
|
3353
|
-
};
|
|
3383
|
+
endFrame: Math.min(totalFrames, start + segmentFrames)
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
return slices;
|
|
3354
3387
|
}
|
|
3355
|
-
function
|
|
3356
|
-
const
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3388
|
+
function resolveSegmentFrames(env) {
|
|
3389
|
+
const raw = env.HF_SEGMENT_FRAMES;
|
|
3390
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_FRAMES;
|
|
3391
|
+
const parsed = Number(raw);
|
|
3392
|
+
if (!Number.isInteger(parsed)) return DEFAULT_SEGMENT_FRAMES;
|
|
3393
|
+
return Math.max(MIN_SEGMENT_FRAMES, parsed);
|
|
3360
3394
|
}
|
|
3361
|
-
|
|
3395
|
+
|
|
3396
|
+
// src/services/render/segmentQueue.ts
|
|
3397
|
+
function createSegmentQueue(slices) {
|
|
3398
|
+
let cursor = 0;
|
|
3362
3399
|
return {
|
|
3363
|
-
|
|
3364
|
-
|
|
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
|
-
}
|
|
3400
|
+
next: () => slices[cursor++],
|
|
3401
|
+
remaining: () => Math.max(0, slices.length - cursor)
|
|
3419
3402
|
};
|
|
3420
3403
|
}
|
|
3421
3404
|
|
|
3405
|
+
// src/services/render/segmentRecycle.ts
|
|
3406
|
+
var DEFAULT_SEGMENT_BROWSER_RECYCLE = 3;
|
|
3407
|
+
function resolveSegmentBrowserRecycle(env) {
|
|
3408
|
+
const raw = env.HF_SEGMENT_BROWSER_RECYCLE;
|
|
3409
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_SEGMENT_BROWSER_RECYCLE;
|
|
3410
|
+
const parsed = Number(raw);
|
|
3411
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SEGMENT_BROWSER_RECYCLE;
|
|
3412
|
+
}
|
|
3413
|
+
var TARGET_LOSS_PATTERNS = [/target closed/i, /detached frame/i, /session closed/i];
|
|
3414
|
+
function isTargetLossError(error) {
|
|
3415
|
+
if (!(error instanceof Error)) return false;
|
|
3416
|
+
return TARGET_LOSS_PATTERNS.some((re) => re.test(error.message));
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3422
3419
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3420
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
3421
|
+
import { tmpdir } from "node:os";
|
|
3422
|
+
import { join as join8 } from "node:path";
|
|
3423
|
+
import {
|
|
3424
|
+
DrawElementVerificationError,
|
|
3425
|
+
captureFrameToBuffer,
|
|
3426
|
+
captureFrameToBufferPipelined,
|
|
3427
|
+
captureFramesBatchPipelined,
|
|
3428
|
+
closeCaptureSession,
|
|
3429
|
+
createCaptureSession,
|
|
3430
|
+
createFrameReorderBuffer,
|
|
3431
|
+
distributeFrames,
|
|
3432
|
+
distributeFramesInterleaved,
|
|
3433
|
+
executeParallelCapture,
|
|
3434
|
+
getCapturePerfSummary,
|
|
3435
|
+
psnrDb,
|
|
3436
|
+
resolveDeVerifyMinDb,
|
|
3437
|
+
recaptureDrawElementFrameForVerify,
|
|
3438
|
+
completeDeferredDrawElementInit,
|
|
3439
|
+
initializeSession,
|
|
3440
|
+
prepareCaptureSessionForReuse,
|
|
3441
|
+
spawnStreamingEncoder
|
|
3442
|
+
} from "@hyperframes/engine";
|
|
3423
3443
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
3424
3444
|
var DE_STALL_POLL_MS = 5e3;
|
|
3425
3445
|
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.";
|
|
3446
|
+
function poolFailure(error, probeSession) {
|
|
3447
|
+
return wrapCaptureStageError(error, probeSession?.browserConsoleBuffer ?? []);
|
|
3448
|
+
}
|
|
3426
3449
|
var ParallelCaptureStallError = class extends Error {
|
|
3427
3450
|
variant;
|
|
3428
3451
|
constructor(args) {
|
|
@@ -3869,7 +3892,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
3869
3892
|
}
|
|
3870
3893
|
);
|
|
3871
3894
|
} catch (err) {
|
|
3872
|
-
if (parallelDrainError) throw parallelDrainError;
|
|
3895
|
+
if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
|
|
3873
3896
|
if (stalled && abortSignal?.aborted !== true) {
|
|
3874
3897
|
throw new ParallelCaptureStallError({
|
|
3875
3898
|
variant: "pool",
|
|
@@ -3880,13 +3903,15 @@ async function runCaptureStreamingStage(input) {
|
|
|
3880
3903
|
phaseSummary: phaseSummary()
|
|
3881
3904
|
});
|
|
3882
3905
|
}
|
|
3883
|
-
throw err;
|
|
3906
|
+
throw poolFailure(err, probeSession);
|
|
3884
3907
|
} finally {
|
|
3885
3908
|
clearInterval(stallTimer);
|
|
3886
3909
|
abortSignal?.removeEventListener("abort", forwardParentAbort);
|
|
3887
3910
|
}
|
|
3888
|
-
if (parallelDrainError) throw parallelDrainError;
|
|
3911
|
+
if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
|
|
3889
3912
|
pushWorkerDedupPerfs(workerResults, dedupPerfs);
|
|
3913
|
+
const workerPageErrors = [];
|
|
3914
|
+
pushWorkerPageErrors(workerResults, workerPageErrors);
|
|
3890
3915
|
if (parallelGuardRan) {
|
|
3891
3916
|
deDrainStats = parallelStats;
|
|
3892
3917
|
}
|
|
@@ -3896,6 +3921,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
3896
3921
|
await closeCaptureSession(probeSession);
|
|
3897
3922
|
probeSession = null;
|
|
3898
3923
|
}
|
|
3924
|
+
lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
|
|
3899
3925
|
} else {
|
|
3900
3926
|
const videoInjector = createRenderVideoFrameInjector();
|
|
3901
3927
|
const session = probeSession ?? await createCaptureSession(
|
|
@@ -4342,8 +4368,9 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4342
4368
|
|
|
4343
4369
|
// src/services/render/segmentManifest.ts
|
|
4344
4370
|
import { createHash as createHash2 } from "node:crypto";
|
|
4345
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4,
|
|
4371
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4346
4372
|
import { join as join10 } from "node:path";
|
|
4373
|
+
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
4347
4374
|
var MANIFEST_FILENAME = "segments.json";
|
|
4348
4375
|
function computeSegmentPlanHash(input) {
|
|
4349
4376
|
const ordered = [
|
|
@@ -4399,7 +4426,7 @@ function writeSegmentManifest(segmentDir, manifest) {
|
|
|
4399
4426
|
mkdirSync4(segmentDir, { recursive: true });
|
|
4400
4427
|
const tmp = join10(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
|
|
4401
4428
|
writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
4402
|
-
|
|
4429
|
+
renameWithRetry2(tmp, join10(segmentDir, MANIFEST_FILENAME));
|
|
4403
4430
|
}
|
|
4404
4431
|
async function probeSegmentFrameCount(path, probe) {
|
|
4405
4432
|
try {
|
|
@@ -5247,6 +5274,8 @@ import {
|
|
|
5247
5274
|
rewriteInlineStyleAssetUrls
|
|
5248
5275
|
} from "@hyperframes/core";
|
|
5249
5276
|
import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
|
|
5277
|
+
import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
5278
|
+
import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
|
|
5250
5279
|
import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
|
|
5251
5280
|
import {
|
|
5252
5281
|
assignBundledRuntimeCompositionIds,
|
|
@@ -5451,19 +5480,11 @@ import {
|
|
|
5451
5480
|
|
|
5452
5481
|
// src/services/animatedGifPrep.ts
|
|
5453
5482
|
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";
|
|
5483
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
|
|
5463
5484
|
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join12, resolve as resolve5 } from "node:path";
|
|
5464
5485
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
5465
5486
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
5466
|
-
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";
|
|
5467
5488
|
var PREPARED_GIF_SUBDIR = "_animated_gif";
|
|
5468
5489
|
var CACHE_SCHEMA = "hfgif-v1";
|
|
5469
5490
|
function splitUrlSuffix(src) {
|
|
@@ -5642,7 +5663,7 @@ async function ensurePreparedWebm(input) {
|
|
|
5642
5663
|
throw new Error("Animated GIF transcode produced an empty output");
|
|
5643
5664
|
}
|
|
5644
5665
|
if (!isUsableFile(input.cachePath)) {
|
|
5645
|
-
|
|
5666
|
+
renameWithRetry3(tmpPath, input.cachePath);
|
|
5646
5667
|
} else {
|
|
5647
5668
|
rmSync6(tmpPath, { force: true });
|
|
5648
5669
|
}
|
|
@@ -7230,6 +7251,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7230
7251
|
const intrinsicDuration = isImage ? 0 : htmlEl.duration;
|
|
7231
7252
|
const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
|
|
7232
7253
|
const mediaStartRaw = htmlEl.getAttribute("data-media-start");
|
|
7254
|
+
const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
|
|
7255
|
+
const automationRaw = htmlEl.getAttribute("data-automation");
|
|
7256
|
+
const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
|
|
7233
7257
|
const loop = htmlEl.hasAttribute("loop");
|
|
7234
7258
|
const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
|
|
7235
7259
|
const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
|
|
@@ -7245,6 +7269,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7245
7269
|
durationInferred,
|
|
7246
7270
|
playbackStartRaw,
|
|
7247
7271
|
mediaStartRaw,
|
|
7272
|
+
playbackRateRaw,
|
|
7273
|
+
automationRaw,
|
|
7274
|
+
defaultPlaybackRate,
|
|
7248
7275
|
loop,
|
|
7249
7276
|
hasAudio,
|
|
7250
7277
|
volume,
|
|
@@ -7254,7 +7281,17 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7254
7281
|
return results;
|
|
7255
7282
|
});
|
|
7256
7283
|
return elements.map(
|
|
7257
|
-
({
|
|
7284
|
+
({
|
|
7285
|
+
endRaw,
|
|
7286
|
+
durationRaw,
|
|
7287
|
+
intrinsicDuration,
|
|
7288
|
+
playbackStartRaw,
|
|
7289
|
+
mediaStartRaw,
|
|
7290
|
+
playbackRateRaw,
|
|
7291
|
+
automationRaw,
|
|
7292
|
+
defaultPlaybackRate,
|
|
7293
|
+
...element
|
|
7294
|
+
}) => ({
|
|
7258
7295
|
...element,
|
|
7259
7296
|
end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
|
|
7260
7297
|
duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
|
|
@@ -7264,7 +7301,14 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7264
7301
|
if (name === "data-media-start") return mediaStartRaw;
|
|
7265
7302
|
return null;
|
|
7266
7303
|
}
|
|
7267
|
-
})
|
|
7304
|
+
}),
|
|
7305
|
+
playbackRate: resolveRateSpec(
|
|
7306
|
+
automationRaw,
|
|
7307
|
+
readPlaybackRate(
|
|
7308
|
+
(name) => name === "data-playback-rate" ? playbackRateRaw : null,
|
|
7309
|
+
defaultPlaybackRate
|
|
7310
|
+
)
|
|
7311
|
+
)
|
|
7268
7312
|
})
|
|
7269
7313
|
);
|
|
7270
7314
|
}
|
|
@@ -7684,9 +7728,18 @@ import {
|
|
|
7684
7728
|
getCompositionDuration,
|
|
7685
7729
|
initializeSession as initializeSession4,
|
|
7686
7730
|
isTransientBrowserError,
|
|
7687
|
-
probeBeginFrameLiveness
|
|
7731
|
+
probeBeginFrameLiveness,
|
|
7732
|
+
videoAudioId
|
|
7688
7733
|
} from "@hyperframes/engine";
|
|
7689
|
-
import {
|
|
7734
|
+
import {
|
|
7735
|
+
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
7736
|
+
fpsToNumber as fpsToNumber7,
|
|
7737
|
+
normalizeRateSpec as normalizeRateSpec3,
|
|
7738
|
+
readMediaStart as readMediaStart2,
|
|
7739
|
+
shiftRateLane as shiftRateLane2,
|
|
7740
|
+
sourceTimeAt as sourceTimeAt3,
|
|
7741
|
+
timeAtSourceTime
|
|
7742
|
+
} from "@hyperframes/core";
|
|
7690
7743
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7691
7744
|
|
|
7692
7745
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7745,16 +7798,28 @@ function authoredMediaStarts(html) {
|
|
|
7745
7798
|
}
|
|
7746
7799
|
return starts;
|
|
7747
7800
|
}
|
|
7748
|
-
function
|
|
7801
|
+
function isVideoAudio(audio, videoId) {
|
|
7802
|
+
return audio.type === "video" && audio.id === videoAudioId(videoId);
|
|
7803
|
+
}
|
|
7804
|
+
function adoptRuntimeMediaTiming(composition, el, authored) {
|
|
7749
7805
|
const shift = el.mediaStart - authored;
|
|
7750
|
-
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7751
7806
|
const entries = el.tagName === "video" ? [
|
|
7752
7807
|
...composition.videos.filter((video) => video.id === el.id),
|
|
7753
|
-
...composition.audios.filter(
|
|
7754
|
-
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7755
|
-
)
|
|
7808
|
+
...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
|
|
7756
7809
|
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7757
|
-
for (const entry of entries)
|
|
7810
|
+
for (const entry of entries) {
|
|
7811
|
+
if (typeof entry.playbackRate !== "object") {
|
|
7812
|
+
if (typeof el.playbackRate === "object") {
|
|
7813
|
+
const consumed = Math.max(0, entry.mediaStart - authored);
|
|
7814
|
+
const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
|
|
7815
|
+
entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
|
|
7816
|
+
entry.playbackRate = shiftRateLane2(el.playbackRate, head);
|
|
7817
|
+
} else {
|
|
7818
|
+
entry.playbackRate = el.playbackRate;
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
|
|
7822
|
+
}
|
|
7758
7823
|
}
|
|
7759
7824
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7760
7825
|
if (projectedEnd <= 0) return existingEnd;
|
|
@@ -8038,7 +8103,7 @@ async function runProbeStage(input) {
|
|
|
8038
8103
|
sourceChanged,
|
|
8039
8104
|
el.durationInferred
|
|
8040
8105
|
);
|
|
8041
|
-
|
|
8106
|
+
adoptRuntimeMediaTiming(
|
|
8042
8107
|
composition,
|
|
8043
8108
|
el,
|
|
8044
8109
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8061,6 +8126,7 @@ async function runProbeStage(input) {
|
|
|
8061
8126
|
start: el.start,
|
|
8062
8127
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8063
8128
|
mediaStart: el.mediaStart,
|
|
8129
|
+
playbackRate: el.playbackRate,
|
|
8064
8130
|
loop: el.loop,
|
|
8065
8131
|
hasAudio: el.hasAudio && !el.muted
|
|
8066
8132
|
});
|
|
@@ -8085,7 +8151,7 @@ async function runProbeStage(input) {
|
|
|
8085
8151
|
sourceChanged,
|
|
8086
8152
|
el.durationInferred
|
|
8087
8153
|
);
|
|
8088
|
-
|
|
8154
|
+
adoptRuntimeMediaTiming(
|
|
8089
8155
|
composition,
|
|
8090
8156
|
el,
|
|
8091
8157
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8103,6 +8169,7 @@ async function runProbeStage(input) {
|
|
|
8103
8169
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8104
8170
|
mediaStart: el.mediaStart,
|
|
8105
8171
|
loop: el.loop,
|
|
8172
|
+
playbackRate: el.playbackRate,
|
|
8106
8173
|
layer: 0,
|
|
8107
8174
|
volume: el.volume,
|
|
8108
8175
|
type: "audio"
|
|
@@ -8363,7 +8430,8 @@ import {
|
|
|
8363
8430
|
isHttpUrl as isHttpUrl2,
|
|
8364
8431
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
8365
8432
|
runVideoExtractionWithRetry,
|
|
8366
|
-
safeVideoExtractionSourceIdentity
|
|
8433
|
+
safeVideoExtractionSourceIdentity,
|
|
8434
|
+
videoAudioId as videoAudioId2
|
|
8367
8435
|
} from "@hyperframes/engine";
|
|
8368
8436
|
|
|
8369
8437
|
// src/services/render/extractionFailureMetadata.ts
|
|
@@ -8721,7 +8789,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8721
8789
|
const video = composition.videos.find((v) => v.id === ext.videoId);
|
|
8722
8790
|
if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
|
|
8723
8791
|
composition.audios.push({
|
|
8724
|
-
id:
|
|
8792
|
+
id: videoAudioId2(video.id),
|
|
8725
8793
|
src: video.src,
|
|
8726
8794
|
start: video.start,
|
|
8727
8795
|
end: video.end,
|
|
@@ -8747,7 +8815,7 @@ import {
|
|
|
8747
8815
|
|
|
8748
8816
|
// src/services/render/audioPadTrim.ts
|
|
8749
8817
|
import { spawn } from "node:child_process";
|
|
8750
|
-
import { mkdtempSync as mkdtempSync3,
|
|
8818
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync7 } from "node:fs";
|
|
8751
8819
|
import { dirname as dirname7, join as join17 } from "node:path";
|
|
8752
8820
|
import {
|
|
8753
8821
|
DEFAULT_CONFIG,
|
|
@@ -8756,6 +8824,7 @@ import {
|
|
|
8756
8824
|
extractAudioMetadata as extractAudioMetadata3,
|
|
8757
8825
|
getFfprobeBinary,
|
|
8758
8826
|
ManagedChildProcess,
|
|
8827
|
+
renameWithRetry as renameWithRetry4,
|
|
8759
8828
|
runFfmpeg as runFfmpeg4,
|
|
8760
8829
|
trackChildProcess
|
|
8761
8830
|
} from "@hyperframes/engine";
|
|
@@ -9011,7 +9080,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9011
9080
|
}
|
|
9012
9081
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
9013
9082
|
if (measuredPath !== correctedPath) return { success: true };
|
|
9014
|
-
|
|
9083
|
+
renameWithRetry4(correctedPath, input.audioPath);
|
|
9015
9084
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
9016
9085
|
}
|
|
9017
9086
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -9201,6 +9270,7 @@ async function runAudioStage(input) {
|
|
|
9201
9270
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
9202
9271
|
let audioLoweredDb;
|
|
9203
9272
|
if (result.hasAudio) {
|
|
9273
|
+
const guardStart = Date.now();
|
|
9204
9274
|
const peak = await constrainAacTruePeak({
|
|
9205
9275
|
audioPath: result.audioOutputPath,
|
|
9206
9276
|
sourcePath: pcmSourcePath,
|
|
@@ -9210,6 +9280,7 @@ async function runAudioStage(input) {
|
|
|
9210
9280
|
});
|
|
9211
9281
|
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
9212
9282
|
audioLoweredDb = peak.loweredDb;
|
|
9283
|
+
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
9213
9284
|
}
|
|
9214
9285
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9215
9286
|
} finally {
|
|
@@ -9235,6 +9306,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9235
9306
|
let hasAudio = false;
|
|
9236
9307
|
let audioError;
|
|
9237
9308
|
let audioFailures;
|
|
9309
|
+
let audioPhaseMs;
|
|
9238
9310
|
if (audios.length > 0) {
|
|
9239
9311
|
let audioResult;
|
|
9240
9312
|
try {
|
|
@@ -9284,6 +9356,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9284
9356
|
assertAudioNotInterrupted(audioResult.failures);
|
|
9285
9357
|
hasAudio = audioResult.success;
|
|
9286
9358
|
audioFailures = audioResult.failures;
|
|
9359
|
+
audioPhaseMs = audioResult.phaseMs;
|
|
9287
9360
|
if (!hasAudio) {
|
|
9288
9361
|
audioError = audioResult.error ?? "audio mix failed for an unknown reason";
|
|
9289
9362
|
for (const failure of audioResult.failures ?? []) {
|
|
@@ -9302,7 +9375,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9302
9375
|
audioOutputPath,
|
|
9303
9376
|
hasAudio,
|
|
9304
9377
|
audioError,
|
|
9305
|
-
audioFailures
|
|
9378
|
+
audioFailures,
|
|
9379
|
+
audioPhaseMs
|
|
9306
9380
|
};
|
|
9307
9381
|
}
|
|
9308
9382
|
|
|
@@ -9431,6 +9505,7 @@ async function runCaptureStage(input) {
|
|
|
9431
9505
|
let { workerCount } = plan2;
|
|
9432
9506
|
const { forceScreenshot } = plan2;
|
|
9433
9507
|
let lastBrowserConsole = [];
|
|
9508
|
+
const workerPageErrors = [];
|
|
9434
9509
|
let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
|
|
9435
9510
|
const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
|
|
9436
9511
|
if (frameRange !== void 0) {
|
|
@@ -9468,6 +9543,7 @@ async function runCaptureStage(input) {
|
|
|
9468
9543
|
abortSignal,
|
|
9469
9544
|
frameRangeStart: frameRange?.startFrame,
|
|
9470
9545
|
dedupPerfs,
|
|
9546
|
+
pageErrorLines: workerPageErrors,
|
|
9471
9547
|
onProgress: (progress) => {
|
|
9472
9548
|
if (progress.latestWorkerPhase) {
|
|
9473
9549
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -9488,6 +9564,11 @@ async function runCaptureStage(input) {
|
|
|
9488
9564
|
},
|
|
9489
9565
|
cfg: captureCfg,
|
|
9490
9566
|
log
|
|
9567
|
+
}).catch((error) => {
|
|
9568
|
+
throw wrapCaptureStageError(
|
|
9569
|
+
error,
|
|
9570
|
+
withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
|
|
9571
|
+
);
|
|
9491
9572
|
});
|
|
9492
9573
|
captureAttempts.push(...attempts);
|
|
9493
9574
|
const lastAttempt = attempts[attempts.length - 1];
|
|
@@ -9500,6 +9581,7 @@ async function runCaptureStage(input) {
|
|
|
9500
9581
|
await closeCaptureSession5(probeSession);
|
|
9501
9582
|
probeSession = null;
|
|
9502
9583
|
}
|
|
9584
|
+
lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
|
|
9503
9585
|
} else {
|
|
9504
9586
|
const sequential = await runSequentialDiskCapture(
|
|
9505
9587
|
input,
|
|
@@ -10815,7 +10897,7 @@ function videoEncoderOptions(input, engineCfg) {
|
|
|
10815
10897
|
};
|
|
10816
10898
|
}
|
|
10817
10899
|
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"
|
|
10900
|
+
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
10901
|
}
|
|
10820
10902
|
async function readLandedFrame(workerPath, mergedPath) {
|
|
10821
10903
|
if (workerPath !== void 0) {
|
|
@@ -11470,6 +11552,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11470
11552
|
options.cfg
|
|
11471
11553
|
);
|
|
11472
11554
|
pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
|
|
11555
|
+
if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
|
|
11473
11556
|
} finally {
|
|
11474
11557
|
await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
|
|
11475
11558
|
}
|
|
@@ -11732,13 +11815,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
|
|
|
11732
11815
|
return false;
|
|
11733
11816
|
}
|
|
11734
11817
|
}
|
|
11735
|
-
|
|
11818
|
+
var PROBE_READ_TIMEOUT_MS = 5e3;
|
|
11819
|
+
async function evaluateWithinBound(page, fn, what, log) {
|
|
11820
|
+
let timer;
|
|
11821
|
+
const timedOut = new Promise((_, reject) => {
|
|
11822
|
+
timer = setTimeout(
|
|
11823
|
+
() => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
|
|
11824
|
+
PROBE_READ_TIMEOUT_MS
|
|
11825
|
+
);
|
|
11826
|
+
});
|
|
11827
|
+
try {
|
|
11828
|
+
return await Promise.race([page.evaluate(fn), timedOut]);
|
|
11829
|
+
} catch (err) {
|
|
11830
|
+
log.warn(`[Render] live ${what} read skipped; using the static answer`, {
|
|
11831
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11832
|
+
});
|
|
11833
|
+
throw err;
|
|
11834
|
+
} finally {
|
|
11835
|
+
clearTimeout(timer);
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
|
|
11736
11839
|
if (probeSession?.isInitialized) {
|
|
11737
11840
|
try {
|
|
11738
|
-
const liveCount = await
|
|
11841
|
+
const liveCount = await evaluateWithinBound(
|
|
11842
|
+
probeSession.page,
|
|
11739
11843
|
// Live HTMLCollection length — avoids materializing a static NodeList
|
|
11740
11844
|
// on the large-DOM comps this gate exists to catch (review nit).
|
|
11741
|
-
() => document.getElementsByTagName("*").length
|
|
11845
|
+
() => document.getElementsByTagName("*").length,
|
|
11846
|
+
"element count",
|
|
11847
|
+
log
|
|
11742
11848
|
);
|
|
11743
11849
|
if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
|
|
11744
11850
|
return { count: liveCount, source: "live" };
|
|
@@ -11826,11 +11932,11 @@ function probeAdaptersUsed() {
|
|
|
11826
11932
|
}
|
|
11827
11933
|
return used;
|
|
11828
11934
|
}
|
|
11829
|
-
async function resolveAdaptersUsed(probeSession, html) {
|
|
11935
|
+
async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
|
|
11830
11936
|
const staticAdapters = detectAdaptersStatic(html);
|
|
11831
11937
|
if (!probeSession?.isInitialized) return staticAdapters;
|
|
11832
11938
|
try {
|
|
11833
|
-
const live = await probeSession.page
|
|
11939
|
+
const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
|
|
11834
11940
|
if (!Array.isArray(live)) return staticAdapters;
|
|
11835
11941
|
const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
|
|
11836
11942
|
return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
|
|
@@ -12088,7 +12194,10 @@ async function executeRenderPipeline(input) {
|
|
|
12088
12194
|
);
|
|
12089
12195
|
const artifactTransaction = new ArtifactTransaction(
|
|
12090
12196
|
outputPath,
|
|
12091
|
-
isPngSequence || isHls ? "directory" : "file"
|
|
12197
|
+
isPngSequence || isHls ? "directory" : "file",
|
|
12198
|
+
void 0,
|
|
12199
|
+
void 0,
|
|
12200
|
+
log
|
|
12092
12201
|
);
|
|
12093
12202
|
const stagedOutputPath = artifactTransaction.stagingPath;
|
|
12094
12203
|
const needsAlpha = outputNeedsAlpha(outputFormat);
|
|
@@ -12587,8 +12696,8 @@ async function executeRenderPipeline(input) {
|
|
|
12587
12696
|
vfxHostCount,
|
|
12588
12697
|
vfxCapture,
|
|
12589
12698
|
vfxTypes
|
|
12590
|
-
} = await resolveCompositionElementCount(probeSession, compiled.html);
|
|
12591
|
-
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
|
|
12699
|
+
} = await resolveCompositionElementCount(probeSession, compiled.html, log);
|
|
12700
|
+
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
|
|
12592
12701
|
const deShortBandEnabled = deShortBandMaxElements > 0;
|
|
12593
12702
|
const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
|
|
12594
12703
|
const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
|
|
@@ -13868,7 +13977,6 @@ import {
|
|
|
13868
13977
|
existsSync as existsSync21,
|
|
13869
13978
|
linkSync,
|
|
13870
13979
|
mkdirSync as mkdirSync13,
|
|
13871
|
-
renameSync as renameSync6,
|
|
13872
13980
|
rmSync as rmSync12,
|
|
13873
13981
|
writeFileSync as writeFileSync8
|
|
13874
13982
|
} from "node:fs";
|
|
@@ -13881,7 +13989,9 @@ import {
|
|
|
13881
13989
|
getEncoderPreset as getEncoderPreset4,
|
|
13882
13990
|
normalizeVp9CpuUsed,
|
|
13883
13991
|
resolveConfig as resolveConfig3,
|
|
13884
|
-
declareContentAddressedUrl
|
|
13992
|
+
declareContentAddressedUrl,
|
|
13993
|
+
renameWithRetry as renameWithRetry6,
|
|
13994
|
+
declareProbeFactsUrl
|
|
13885
13995
|
} from "@hyperframes/engine";
|
|
13886
13996
|
|
|
13887
13997
|
// src/services/distributed/planFrameCost.ts
|
|
@@ -14822,15 +14932,9 @@ import { join as join31 } from "node:path";
|
|
|
14822
14932
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
14823
14933
|
|
|
14824
14934
|
// 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";
|
|
14935
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
|
|
14833
14936
|
import { open } from "node:fs/promises";
|
|
14937
|
+
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
14834
14938
|
import { dirname as dirname12, join as join30 } from "node:path";
|
|
14835
14939
|
import { finished, pipeline } from "node:stream/promises";
|
|
14836
14940
|
function boxAt(src, off, at, limit) {
|
|
@@ -15203,7 +15307,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15203
15307
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15204
15308
|
out.end();
|
|
15205
15309
|
await finished(out);
|
|
15206
|
-
|
|
15310
|
+
renameWithRetry5(join30(workDir, "out.mp4"), outputPath);
|
|
15207
15311
|
} finally {
|
|
15208
15312
|
out.destroy();
|
|
15209
15313
|
rmSync11(workDir, { recursive: true, force: true });
|
|
@@ -15929,7 +16033,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
15929
16033
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
15930
16034
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
15931
16035
|
const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
|
|
15932
|
-
for (const [, file] of remoteFiles)
|
|
16036
|
+
for (const [, file] of remoteFiles) {
|
|
16037
|
+
declareContentAddressedUrl(file.url, file.sha256);
|
|
16038
|
+
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
16039
|
+
}
|
|
15933
16040
|
const urlByAbsentLocalPath = new Map(
|
|
15934
16041
|
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
15935
16042
|
);
|
|
@@ -16051,7 +16158,8 @@ function mixPlanAudio(c, compiledDir) {
|
|
|
16051
16158
|
audios: c.composition.audios,
|
|
16052
16159
|
masterFxChain: c.composition.masterFxChain,
|
|
16053
16160
|
abortSignal: c.abortSignal,
|
|
16054
|
-
assertNotAborted: c.assertNotAborted
|
|
16161
|
+
assertNotAborted: c.assertNotAborted,
|
|
16162
|
+
log: c.log
|
|
16055
16163
|
});
|
|
16056
16164
|
}
|
|
16057
16165
|
async function extractPlanVideos(c, deferRangeExtraction) {
|
|
@@ -16070,6 +16178,7 @@ async function extractPlanVideos(c, deferRangeExtraction) {
|
|
|
16070
16178
|
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
16071
16179
|
if (extractResult.extractionResult) {
|
|
16072
16180
|
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
16181
|
+
c.log.info("[plan] extract phases", { ...extractResult.extractionResult.phaseBreakdown });
|
|
16073
16182
|
}
|
|
16074
16183
|
return extractResult;
|
|
16075
16184
|
}
|
|
@@ -16115,12 +16224,12 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16115
16224
|
const videoSourcesDir = join33(planDir, "video-sources");
|
|
16116
16225
|
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
16117
16226
|
if (existsSync21(stagedVideoFrames)) {
|
|
16118
|
-
|
|
16227
|
+
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
16119
16228
|
} else {
|
|
16120
16229
|
mkdirSync13(videoFramesDst, { recursive: true });
|
|
16121
16230
|
}
|
|
16122
16231
|
if (existsSync21(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
16123
|
-
|
|
16232
|
+
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
16124
16233
|
const shipVideoSource = createVideoSourceShipper({
|
|
16125
16234
|
planDir,
|
|
16126
16235
|
projectDir,
|
|
@@ -16223,7 +16332,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16223
16332
|
}
|
|
16224
16333
|
const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16225
16334
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16226
|
-
|
|
16335
|
+
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
16227
16336
|
}
|
|
16228
16337
|
if (chunksCarryAudio && existsSync21(planAudioPath)) {
|
|
16229
16338
|
if (options.sliceChunkAudio) {
|
|
@@ -17740,7 +17849,6 @@ import {
|
|
|
17740
17849
|
mkdtempSync as mkdtempSync7,
|
|
17741
17850
|
readFileSync as readFileSync13,
|
|
17742
17851
|
readdirSync as readdirSync9,
|
|
17743
|
-
renameSync as renameSync8,
|
|
17744
17852
|
rmSync as rmSync15,
|
|
17745
17853
|
statSync as statSync10,
|
|
17746
17854
|
writeFileSync as writeFileSync11
|
|
@@ -17748,7 +17856,7 @@ import {
|
|
|
17748
17856
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
17749
17857
|
import { dirname as dirname16, isAbsolute as isAbsolute5, join as join39, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
|
|
17750
17858
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
17751
|
-
import { createFrameLookupTable as createFrameLookupTable4 } from "@hyperframes/engine";
|
|
17859
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
17752
17860
|
|
|
17753
17861
|
// src/services/distributed/planV2Publisher.ts
|
|
17754
17862
|
import {
|
|
@@ -17757,11 +17865,11 @@ import {
|
|
|
17757
17865
|
linkSync as linkSync2,
|
|
17758
17866
|
mkdirSync as mkdirSync15,
|
|
17759
17867
|
mkdtempSync as mkdtempSync6,
|
|
17760
|
-
renameSync as renameSync7,
|
|
17761
17868
|
rmSync as rmSync14,
|
|
17762
17869
|
statSync as statSync9,
|
|
17763
17870
|
writeFileSync as writeFileSync10
|
|
17764
17871
|
} from "node:fs";
|
|
17872
|
+
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
17765
17873
|
import { dirname as dirname15, join as join38 } from "node:path";
|
|
17766
17874
|
|
|
17767
17875
|
// src/services/distributed/planV2Errors.ts
|
|
@@ -17850,7 +17958,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17850
17958
|
if (!canFallbackToCopy(error)) throw error;
|
|
17851
17959
|
copyFileSync6(blob.sourcePath, temporaryPath);
|
|
17852
17960
|
}
|
|
17853
|
-
|
|
17961
|
+
renameWithRetry7(temporaryPath, destinationPath);
|
|
17854
17962
|
} finally {
|
|
17855
17963
|
rmSync14(stagingDir, { recursive: true, force: true });
|
|
17856
17964
|
}
|
|
@@ -17864,7 +17972,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17864
17972
|
}
|
|
17865
17973
|
}
|
|
17866
17974
|
writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
17867
|
-
|
|
17975
|
+
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
17868
17976
|
this.#committed = true;
|
|
17869
17977
|
}
|
|
17870
17978
|
async abort() {
|
|
@@ -18131,7 +18239,7 @@ function writeBlob(sourcePath, destinationPath) {
|
|
|
18131
18239
|
const temporaryPath = join39(temporaryDir, "blob");
|
|
18132
18240
|
try {
|
|
18133
18241
|
copyFileSync7(sourcePath, temporaryPath);
|
|
18134
|
-
|
|
18242
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
18135
18243
|
} finally {
|
|
18136
18244
|
rmSync15(temporaryDir, { recursive: true, force: true });
|
|
18137
18245
|
}
|
|
@@ -18263,7 +18371,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18263
18371
|
canonicalJsonStringify(publication.manifest),
|
|
18264
18372
|
"utf-8"
|
|
18265
18373
|
);
|
|
18266
|
-
|
|
18374
|
+
renameWithRetry8(tempDir, planV2Dir);
|
|
18267
18375
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18268
18376
|
} catch (error) {
|
|
18269
18377
|
rmSync15(tempDir, { recursive: true, force: true });
|
|
@@ -18548,7 +18656,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18548
18656
|
}),
|
|
18549
18657
|
"utf-8"
|
|
18550
18658
|
);
|
|
18551
|
-
|
|
18659
|
+
renameWithRetry8(tempDir, destinationDir);
|
|
18552
18660
|
} catch (error) {
|
|
18553
18661
|
rmSync15(tempDir, { recursive: true, force: true });
|
|
18554
18662
|
throw error;
|
|
@@ -18626,7 +18734,6 @@ import {
|
|
|
18626
18734
|
mkdtempSync as mkdtempSync8,
|
|
18627
18735
|
readFileSync as readFileSync14,
|
|
18628
18736
|
readdirSync as readdirSync10,
|
|
18629
|
-
renameSync as renameSync9,
|
|
18630
18737
|
rmSync as rmSync16,
|
|
18631
18738
|
statSync as statSync11,
|
|
18632
18739
|
writeFileSync as writeFileSync12
|
|
@@ -18640,7 +18747,8 @@ import {
|
|
|
18640
18747
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
18641
18748
|
resolveConfig as resolveConfig6,
|
|
18642
18749
|
runFfmpeg as runFfmpeg7,
|
|
18643
|
-
appendSoftwareEncoderThreadsArg
|
|
18750
|
+
appendSoftwareEncoderThreadsArg,
|
|
18751
|
+
renameWithRetry as renameWithRetry9
|
|
18644
18752
|
} from "@hyperframes/engine";
|
|
18645
18753
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
18646
18754
|
function cfrRateControlArgs(locked) {
|
|
@@ -18697,7 +18805,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18697
18805
|
const finalPath = join40(workDir, `output.${format}`);
|
|
18698
18806
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
18699
18807
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
18700
|
-
|
|
18808
|
+
renameWithRetry9(finalPath, outputPath);
|
|
18701
18809
|
log.info("[assemble] stage timings", {
|
|
18702
18810
|
...stageMs,
|
|
18703
18811
|
chunks: chunkPaths.length,
|
|
@@ -18949,6 +19057,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
18949
19057
|
// src/services/distributed/renderChunk.ts
|
|
18950
19058
|
import { randomBytes } from "node:crypto";
|
|
18951
19059
|
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
|
|
19060
|
+
import { cpus as cpus3 } from "node:os";
|
|
18952
19061
|
import { join as join41 } from "node:path";
|
|
18953
19062
|
import {
|
|
18954
19063
|
assertSwiftShader,
|
|
@@ -19253,6 +19362,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19253
19362
|
let forceScreenshotForChunk = encoder.forceScreenshot;
|
|
19254
19363
|
let shownBoxes;
|
|
19255
19364
|
let extraction;
|
|
19365
|
+
const overlap = { current: null };
|
|
19256
19366
|
try {
|
|
19257
19367
|
if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
|
|
19258
19368
|
const bootStarted = Date.now();
|
|
@@ -19340,6 +19450,59 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19340
19450
|
videoFrameLookup,
|
|
19341
19451
|
frameCacheOptions(cfg, chunkWorkerCount)
|
|
19342
19452
|
);
|
|
19453
|
+
const isPngSequence = plan2.dimensions.format === "png-sequence";
|
|
19454
|
+
outputKind = isPngSequence ? "frame-dir" : "file";
|
|
19455
|
+
const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
|
|
19456
|
+
const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
|
|
19457
|
+
const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
|
|
19458
|
+
const effectiveQuality = encoder.crf ?? preset.quality;
|
|
19459
|
+
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19460
|
+
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19461
|
+
if (isPngSequence) {
|
|
19462
|
+
if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
|
|
19463
|
+
} else {
|
|
19464
|
+
const outDir = join41(outputChunkPath, "..");
|
|
19465
|
+
if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
|
|
19466
|
+
}
|
|
19467
|
+
const encodeInput = {
|
|
19468
|
+
job,
|
|
19469
|
+
log,
|
|
19470
|
+
outputPath: videoOnlyPath,
|
|
19471
|
+
framesDir,
|
|
19472
|
+
videoOnlyPath,
|
|
19473
|
+
width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
|
|
19474
|
+
height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
|
|
19475
|
+
needsAlpha: plan2.dimensions.format !== "mp4",
|
|
19476
|
+
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
19477
|
+
// The encoder writes video only; audio joins at assemble time or in the
|
|
19478
|
+
// chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
|
|
19479
|
+
// sidecar AND the mp4 audio mux.
|
|
19480
|
+
hasAudio: false,
|
|
19481
|
+
isPngSequence,
|
|
19482
|
+
// `DistributedFormat` has no "gif" member — distributed chunks are
|
|
19483
|
+
// always video segments (gif renders in-process only).
|
|
19484
|
+
isGif: false,
|
|
19485
|
+
preset,
|
|
19486
|
+
effectiveQuality,
|
|
19487
|
+
effectiveBitrate,
|
|
19488
|
+
engineConfig: {
|
|
19489
|
+
ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
|
|
19490
|
+
vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
|
|
19491
|
+
},
|
|
19492
|
+
// Distributed chunks emit a single ffmpeg call per chunk; the
|
|
19493
|
+
// in-process per-chunk-within-chunk path would re-split our
|
|
19494
|
+
// already-chunked work.
|
|
19495
|
+
enableChunkedEncode: false,
|
|
19496
|
+
chunkedEncodeSize: framesInChunk,
|
|
19497
|
+
abortSignal: void 0,
|
|
19498
|
+
assertNotAborted: () => {
|
|
19499
|
+
},
|
|
19500
|
+
// GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
|
|
19501
|
+
// first frame is an IDR keyframe and concat-copy at assemble time
|
|
19502
|
+
// round-trips losslessly.
|
|
19503
|
+
lockGopForChunkConcat: !isPngSequence,
|
|
19504
|
+
gopSize: framesInChunk
|
|
19505
|
+
};
|
|
19343
19506
|
const captureStarted = Date.now();
|
|
19344
19507
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
19345
19508
|
forceScreenshot: forceScreenshotForChunk,
|
|
@@ -19372,6 +19535,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19372
19535
|
}
|
|
19373
19536
|
const captureSession = session;
|
|
19374
19537
|
session = null;
|
|
19538
|
+
overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus3().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
|
|
19375
19539
|
await runCaptureStage({
|
|
19376
19540
|
fileServer,
|
|
19377
19541
|
workDir,
|
|
@@ -19391,7 +19555,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19391
19555
|
abortSignal: void 0,
|
|
19392
19556
|
assertNotAborted: () => {
|
|
19393
19557
|
},
|
|
19394
|
-
frameRange: range
|
|
19558
|
+
frameRange: range,
|
|
19559
|
+
onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame)
|
|
19560
|
+
}).catch(async (captureError) => {
|
|
19561
|
+
await overlap.current?.abort();
|
|
19562
|
+
overlap.current = null;
|
|
19563
|
+
throw captureError;
|
|
19395
19564
|
});
|
|
19396
19565
|
return observedChunkCaptureMode(capturePerfs);
|
|
19397
19566
|
},
|
|
@@ -19423,60 +19592,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19423
19592
|
log
|
|
19424
19593
|
);
|
|
19425
19594
|
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
19595
|
const encodeStarted = Date.now();
|
|
19441
|
-
|
|
19442
|
-
|
|
19443
|
-
|
|
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
|
-
});
|
|
19596
|
+
const overlapped = overlap.current;
|
|
19597
|
+
overlap.current = null;
|
|
19598
|
+
await (overlapped ? overlapped.finish() : runEncodeStage(encodeInput));
|
|
19480
19599
|
if (encoder.chunksCarryAudio) {
|
|
19481
19600
|
const audioScratchPath = `${outputChunkPath}.audio.m4a`;
|
|
19482
19601
|
const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
|
|
@@ -19494,6 +19613,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19494
19613
|
}
|
|
19495
19614
|
encodeStageMs = Date.now() - encodeStarted;
|
|
19496
19615
|
} finally {
|
|
19616
|
+
await overlap.current?.abort().catch(() => {
|
|
19617
|
+
});
|
|
19497
19618
|
await extraction?.catch(() => {
|
|
19498
19619
|
});
|
|
19499
19620
|
if (session) {
|
|
@@ -19602,6 +19723,9 @@ var READ_CHUNK_BYTES = 256 * 1024;
|
|
|
19602
19723
|
// src/services/distributed/audioSidecar.ts
|
|
19603
19724
|
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
|
|
19604
19725
|
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
19726
|
+
|
|
19727
|
+
// src/services/distributed/probeFacts.ts
|
|
19728
|
+
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
19605
19729
|
export {
|
|
19606
19730
|
CURRENT_PLAN_PROTOCOL,
|
|
19607
19731
|
DEFAULT_CONFIG2 as DEFAULT_CONFIG,
|