@hyperframes/engine 0.8.199 → 0.8.201
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/config.d.ts +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/services/audioMixer.js +3 -9
- package/dist/services/browserManager.d.ts +3 -3
- package/dist/services/browserManager.js +37 -17
- package/dist/services/chunkEncoder.js +6 -12
- package/dist/services/frameCapture.d.ts +11 -1
- package/dist/services/frameCapture.js +186 -62
- package/dist/services/parallelCoordinator.js +12 -10
- package/dist/services/riskFrames.d.ts +30 -0
- package/dist/services/riskFrames.js +101 -0
- package/dist/services/screenshotService.d.ts +7 -0
- package/dist/services/screenshotService.js +4 -0
- package/dist/services/systemMemory.d.ts +2 -0
- package/dist/services/systemMemory.js +38 -1
- package/dist/services/threeDProjection.d.ts +14 -0
- package/dist/services/threeDProjection.js +117 -41
- package/dist/services/videoFrameExtractor.js +9 -5
- package/dist/services/videoFrameInjector.js +2 -1
- package/dist/types.d.ts +6 -2
- package/dist/utils/psnrFilterAvailability.d.ts +1 -0
- package/dist/utils/psnrFilterAvailability.js +17 -10
- package/dist/utils/runFfmpeg.d.ts +2 -3
- package/dist/utils/runFfmpeg.js +13 -16
- package/dist/utils/urlDownloader.js +21 -7
- package/package.json +3 -3
package/dist/utils/runFfmpeg.js
CHANGED
|
@@ -94,11 +94,11 @@ export function formatFfmpegError(exitCode, stderr, tailLines = DEFAULT_STDERR_T
|
|
|
94
94
|
? `FFmpeg exited with code ${exitCode}\nffmpeg stderr (tail):\n${tail}`
|
|
95
95
|
: `FFmpeg exited with code ${exitCode}`;
|
|
96
96
|
}
|
|
97
|
-
/** formatFfmpegError, led by the timeout when this process was killed
|
|
97
|
+
/** formatFfmpegError, led by the timeout when this process was killed for making no progress. */
|
|
98
98
|
export function describeFfmpegFailure(result, timeoutMs) {
|
|
99
99
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
100
|
-
if (result.terminationReason === "
|
|
101
|
-
return (`FFmpeg timed out after ${timeoutMs} ms (ffmpegProcessTimeout
|
|
100
|
+
if (result.terminationReason === "inactivity") {
|
|
101
|
+
return (`FFmpeg timed out after ${timeoutMs} ms without progress (ffmpegProcessTimeout, ` +
|
|
102
102
|
`FFMPEG_PROCESS_TIMEOUT_MS). ${error}`);
|
|
103
103
|
}
|
|
104
104
|
if (result.exitCode !== null)
|
|
@@ -147,25 +147,22 @@ const ABORTED_BEFORE_START = {
|
|
|
147
147
|
export async function runFfmpeg(args, opts) {
|
|
148
148
|
if (opts?.signal?.aborted)
|
|
149
149
|
return { ...ABORTED_BEFORE_START };
|
|
150
|
-
const stallTimeout = opts?.stallTimeout;
|
|
151
150
|
let framesWritten = 0;
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
let secondsWritten = 0;
|
|
152
|
+
const readStats = ffmpegStatsReader(({ frames = 0, seconds = 0 }) => {
|
|
153
|
+
if (frames <= framesWritten && seconds <= secondsWritten)
|
|
154
154
|
return;
|
|
155
|
-
framesWritten = frames;
|
|
155
|
+
framesWritten = Math.max(frames, framesWritten);
|
|
156
|
+
secondsWritten = Math.max(seconds, secondsWritten);
|
|
156
157
|
managed.markActivity();
|
|
157
158
|
});
|
|
158
159
|
const managed = new ManagedChildProcess(spawnFfmpeg(args), {
|
|
159
160
|
signal: opts?.signal,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
: (chunk) => {
|
|
166
|
-
readStats(chunk);
|
|
167
|
-
opts?.onStderr?.(chunk);
|
|
168
|
-
},
|
|
161
|
+
inactivityTimeoutMs: opts?.timeout ?? DEFAULT_TIMEOUT,
|
|
162
|
+
onStderr: (chunk) => {
|
|
163
|
+
readStats(chunk);
|
|
164
|
+
opts?.onStderr?.(chunk);
|
|
165
|
+
},
|
|
169
166
|
});
|
|
170
167
|
return toResult(await managed.wait());
|
|
171
168
|
}
|
|
@@ -586,7 +586,7 @@ function checksumMismatchError(source, status, telemetry) {
|
|
|
586
586
|
// Response protocol, streamed byte accounting, hashes, and payload validation
|
|
587
587
|
// share one lifecycle so no validation can happen after publication.
|
|
588
588
|
// fallow-ignore-next-line complexity
|
|
589
|
-
async function fetchToPartial(url, partialPath, controller, options) {
|
|
589
|
+
async function fetchToPartial(url, partialPath, controller, options, onSizedBodyBytes) {
|
|
590
590
|
const { response, finalUrl } = await fetchWithValidatedRedirects(url, controller);
|
|
591
591
|
const finalIdentity = safeDownloadUrlIdentity(finalUrl);
|
|
592
592
|
const rangeDisposition = classifyRangeDisposition(response);
|
|
@@ -643,6 +643,8 @@ async function fetchToPartial(url, partialPath, controller, options) {
|
|
|
643
643
|
const inspector = new Transform({
|
|
644
644
|
transform(chunk, _encoding, callback) {
|
|
645
645
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
646
|
+
if (declaredLength !== undefined)
|
|
647
|
+
onSizedBodyBytes();
|
|
646
648
|
receivedBytes += bytes.length;
|
|
647
649
|
sha256.update(bytes);
|
|
648
650
|
md5.update(bytes);
|
|
@@ -771,16 +773,26 @@ async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, s
|
|
|
771
773
|
controller.abort();
|
|
772
774
|
};
|
|
773
775
|
signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
776
|
+
let timeoutId;
|
|
777
|
+
const armTimeout = () => {
|
|
778
|
+
clearTimeout(timeoutId);
|
|
779
|
+
timeoutId = setTimeout(() => {
|
|
780
|
+
timedOut = true;
|
|
781
|
+
controller.abort();
|
|
782
|
+
}, timeoutMs);
|
|
783
|
+
};
|
|
784
|
+
armTimeout();
|
|
785
|
+
let sizedBodyStarted = false;
|
|
786
|
+
const extendTimeout = () => {
|
|
787
|
+
sizedBodyStarted = true;
|
|
788
|
+
armTimeout();
|
|
789
|
+
};
|
|
778
790
|
const identity = safeDownloadUrlIdentity(url);
|
|
779
791
|
try {
|
|
780
792
|
if (callerAborted) {
|
|
781
793
|
throw new UrlDownloadError("cancelled", false, "Download cancelled");
|
|
782
794
|
}
|
|
783
|
-
const integrity = await fetchToPartial(url, partialPath, controller, options);
|
|
795
|
+
const integrity = await fetchToPartial(url, partialPath, controller, options, extendTimeout);
|
|
784
796
|
let outcome = syncAndPublishPartial(partialPath, localPath);
|
|
785
797
|
let publishedIntegrity = integrity;
|
|
786
798
|
if (outcome === "race_reused") {
|
|
@@ -827,7 +839,9 @@ async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, s
|
|
|
827
839
|
throw classified;
|
|
828
840
|
}
|
|
829
841
|
if (timedOut) {
|
|
830
|
-
const classified = new UrlDownloadError("timeout", true,
|
|
842
|
+
const classified = new UrlDownloadError("timeout", true, sizedBodyStarted
|
|
843
|
+
? `Download stalled: no data for ${timeoutMs / 1000}s`
|
|
844
|
+
: `Download timeout after ${timeoutMs / 1000}s`);
|
|
831
845
|
emitDownloadTelemetry(options, {
|
|
832
846
|
urlFingerprint: identity.urlFingerprint,
|
|
833
847
|
initialHost: identity.host,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.201",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"linkedom": "^0.18.12",
|
|
51
51
|
"puppeteer": "^25.8.0",
|
|
52
52
|
"puppeteer-core": "^25.8.0",
|
|
53
|
-
"@hyperframes/core": "^0.8.
|
|
54
|
-
"@hyperframes/parsers": "^0.8.
|
|
53
|
+
"@hyperframes/core": "^0.8.201",
|
|
54
|
+
"@hyperframes/parsers": "^0.8.201"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.0.10",
|