@hyperframes/engine 0.9.5 → 0.9.6
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 +1 -0
- package/dist/config.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/services/chunkStreamingEncoder.d.ts +16 -0
- package/dist/services/chunkStreamingEncoder.js +30 -0
- package/dist/services/frameCapture.js +2 -1
- package/dist/services/inPageEncodeEligibility.d.ts +19 -0
- package/dist/services/inPageEncodeEligibility.js +15 -0
- package/dist/services/screenshotService.d.ts +1 -0
- package/dist/services/screenshotService.js +25 -6
- package/dist/services/streamingEncoder.d.ts +5 -0
- package/dist/services/streamingEncoder.js +18 -8
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ export interface EngineConfig {
|
|
|
74
74
|
* true. Kill switch: `HF_DE_WORKER_ENCODE=false`.
|
|
75
75
|
*/
|
|
76
76
|
enableDrawElementWorkerEncode: boolean;
|
|
77
|
+
useInPageEncode: boolean;
|
|
77
78
|
/**
|
|
78
79
|
* INTERNAL. Set by resolveConfig when it disabled enablePageSideCompositing
|
|
79
80
|
* solely because drawElement was on. Lets the producer's compile-time gates
|
package/dist/config.js
CHANGED
|
@@ -32,6 +32,7 @@ export const DEFAULT_CONFIG = {
|
|
|
32
32
|
videoLayerPassthrough: false,
|
|
33
33
|
useDrawElement: true,
|
|
34
34
|
enableDrawElementWorkerEncode: true,
|
|
35
|
+
useInPageEncode: false,
|
|
35
36
|
// Auto-detected per host in `resolveConfig`; defaults off for the raw
|
|
36
37
|
// DEFAULT_CONFIG (used directly by tests and worker-sizing fallbacks).
|
|
37
38
|
lowMemoryMode: false,
|
|
@@ -76,6 +77,7 @@ const BOOLEAN_ENGINE_CONFIG_FIELDS = [
|
|
|
76
77
|
"videoLayerPassthrough",
|
|
77
78
|
"useDrawElement",
|
|
78
79
|
"enableDrawElementWorkerEncode",
|
|
80
|
+
"useInPageEncode",
|
|
79
81
|
"lowMemoryMode",
|
|
80
82
|
"enablePageSideCompositing",
|
|
81
83
|
"enableChunkedEncode",
|
|
@@ -517,6 +519,7 @@ export function resolveConfig(overrides) {
|
|
|
517
519
|
videoLayerPassthrough: envBool("HF_VIDEO_LAYER_PASSTHROUGH", DEFAULT_CONFIG.videoLayerPassthrough),
|
|
518
520
|
useDrawElement: envBool("PRODUCER_EXPERIMENTAL_FAST_CAPTURE", DEFAULT_CONFIG.useDrawElement),
|
|
519
521
|
enableDrawElementWorkerEncode: envBool("HF_DE_WORKER_ENCODE", DEFAULT_CONFIG.enableDrawElementWorkerEncode),
|
|
522
|
+
useInPageEncode: envBool("HF_INPAGE_ENCODE", DEFAULT_CONFIG.useInPageEncode),
|
|
520
523
|
lowMemoryMode: resolveLowMemoryMode(),
|
|
521
524
|
enablePageSideCompositing: envBool("HF_PAGE_SIDE_COMPOSITING", DEFAULT_CONFIG.enablePageSideCompositing),
|
|
522
525
|
vp9CpuUsed: envVp9CpuUsed(),
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
|
|
|
48
48
|
export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, getCachedGpuEncoder, ENCODER_PRESETS, getEncoderPreset, PRORES_PROFILES, PRORES_PROFILE_NAMES, isProresProfile, type ProresProfile, appendRateCapArgs, keyframeIntervalToGopSize, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, type GpuEncoder, type PackageHlsOptions, } from "./services/chunkEncoder.js";
|
|
49
49
|
export type { EncoderOptions, EncodeResult, MuxResult } from "./services/chunkEncoder.types.js";
|
|
50
50
|
export { spawnStreamingEncoder, createFrameReorderBuffer, type StreamingEncoder, type StreamingEncoderOptions, type StreamingEncoderResult, type FrameReorderBuffer, } from "./services/streamingEncoder.js";
|
|
51
|
+
export { spawnChunkStreamingEncoder, buildChunkStreamingArgs, BT709_LIMITED, type ChunkColour, type ChunkStreamingOptions, } from "./services/chunkStreamingEncoder.js";
|
|
52
|
+
export { resolveInPageEncode, type InPageEncodeDecision, type InPageEncodeInput, } from "./services/inPageEncodeEligibility.js";
|
|
51
53
|
export { parseVideoElements, hasGradedCanvas, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, type VideoElement, type ImageElement, type ExtractedFrames, type DeferredFrameRange, type FrameRange, type ExtractionOptions, type ExtractionResult, type ExtractionPhaseBreakdown, type TimelineExtractionWindow, type VideoExtractionFailure, type VideoExtractionFailureKind, type VideoExtractionFailureGroupDetails, type VideoExtractionFailureRetry, type VideoExtractionFailureStatusClass, type SafeVideoExtractionSourceIdentity, type VideoFrameFormat, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, buildVideoLayerInput, type VideoLayerInput, } from "./services/videoFrameExtractor.js";
|
|
52
54
|
export { qualifyVideoLayer } from "./services/videoLayerPassthrough.js";
|
|
53
55
|
export { isSelfOrAncestorHidden } from "./services/mediaHidden.js";
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,8 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
|
|
|
51
51
|
// ── Encoding ───────────────────────────────────────────────────────────────────
|
|
52
52
|
export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, getCachedGpuEncoder, ENCODER_PRESETS, getEncoderPreset, PRORES_PROFILES, PRORES_PROFILE_NAMES, isProresProfile, appendRateCapArgs, keyframeIntervalToGopSize, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, } from "./services/chunkEncoder.js";
|
|
53
53
|
export { spawnStreamingEncoder, createFrameReorderBuffer, } from "./services/streamingEncoder.js";
|
|
54
|
+
export { spawnChunkStreamingEncoder, buildChunkStreamingArgs, BT709_LIMITED, } from "./services/chunkStreamingEncoder.js";
|
|
55
|
+
export { resolveInPageEncode, } from "./services/inPageEncodeEligibility.js";
|
|
54
56
|
// ── Media processing ───────────────────────────────────────────────────────────
|
|
55
57
|
export { parseVideoElements, hasGradedCanvas, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, videoStartOnMediaTimeline, ffmpegSeekSeconds, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, sdrCanvasColourFilters, buildVideoLayerInput, } from "./services/videoFrameExtractor.js";
|
|
56
58
|
export { qualifyVideoLayer } from "./services/videoLayerPassthrough.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Fps } from "@hyperframes/core";
|
|
2
|
+
import type { EngineConfig } from "../config.js";
|
|
3
|
+
import { type StreamingEncoder } from "./streamingEncoder.js";
|
|
4
|
+
export interface ChunkColour {
|
|
5
|
+
primaries: number;
|
|
6
|
+
transfer: number;
|
|
7
|
+
matrix: number;
|
|
8
|
+
fullRange: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const BT709_LIMITED: ChunkColour;
|
|
11
|
+
export interface ChunkStreamingOptions {
|
|
12
|
+
fps: Fps;
|
|
13
|
+
colour?: ChunkColour;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildChunkStreamingArgs(options: ChunkStreamingOptions, outputPath: string): string[];
|
|
16
|
+
export declare function spawnChunkStreamingEncoder(outputPath: string, options: ChunkStreamingOptions, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegStreamingTimeout">>): StreamingEncoder;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fpsToFfmpegArg } from "@hyperframes/core";
|
|
2
|
+
import { appendRenderProvenanceArgs } from "../utils/renderProvenance.js";
|
|
3
|
+
import { spawnFfmpegStream } from "./streamingEncoder.js";
|
|
4
|
+
export const BT709_LIMITED = {
|
|
5
|
+
primaries: 1,
|
|
6
|
+
transfer: 1,
|
|
7
|
+
matrix: 1,
|
|
8
|
+
fullRange: false,
|
|
9
|
+
};
|
|
10
|
+
export function buildChunkStreamingArgs(options, outputPath) {
|
|
11
|
+
const { primaries, transfer, matrix, fullRange } = options.colour ?? BT709_LIMITED;
|
|
12
|
+
const vui = [
|
|
13
|
+
`colour_primaries=${primaries}`,
|
|
14
|
+
`transfer_characteristics=${transfer}`,
|
|
15
|
+
`matrix_coefficients=${matrix}`,
|
|
16
|
+
`video_full_range_flag=${fullRange ? 1 : 0}`,
|
|
17
|
+
].join(":");
|
|
18
|
+
const args = ["-f", "h264", "-framerate", fpsToFfmpegArg(options.fps), "-i", "-"];
|
|
19
|
+
args.push("-c:v", "copy", "-bsf:v", `h264_metadata=${vui}`);
|
|
20
|
+
args.push(...["-colorspace:v", "bt709", "-color_primaries:v", "bt709", "-color_trc:v", "bt709"], ...["-color_range", fullRange ? "pc" : "tv"], ...["-video_track_timescale", "90000", "-avoid_negative_ts", "make_zero"]);
|
|
21
|
+
appendRenderProvenanceArgs(args, outputPath);
|
|
22
|
+
args.push("-y", outputPath);
|
|
23
|
+
return args;
|
|
24
|
+
}
|
|
25
|
+
export function spawnChunkStreamingEncoder(outputPath, options, signal, config) {
|
|
26
|
+
return spawnFfmpegStream(outputPath, buildChunkStreamingArgs(options, outputPath), {
|
|
27
|
+
signal,
|
|
28
|
+
config,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -18,7 +18,7 @@ import { DEFAULT_SAMPLES_PER_FRAME, MotionBlurAccumulator, SPATIAL_TWEEN_PROPERT
|
|
|
18
18
|
import { describePageMotionBlur, describePageMotionBlurAtEnd, installPageMotionBlur, } from "./pageMotionBlur.js";
|
|
19
19
|
// ── Extracted modules ───────────────────────────────────────────────────────
|
|
20
20
|
import { acquireBrowser, releaseBrowser, forceReleaseBrowser, buildChromeArgs, resolveBrowserGpuMode, captureWithoutWaylandDisplay, resolveHeadlessShellPath, compositionRequiresWebGpu, assertWebGpuAdapterAvailable, usesSoftwareWebGpu, } from "./browserManager.js";
|
|
21
|
-
import { beginFrameCapture, ensureRenderFrameSiblings, getCdpSession, pageContentExceedsCaptureHeight, pageScreenshotCapture, initTransparentBackground, capturesAlpha, shouldDefaultCaptureBeyondViewport, } from "./screenshotService.js";
|
|
21
|
+
import { beginFrameCapture, forgetBeginFrameCapture, ensureRenderFrameSiblings, getCdpSession, pageContentExceedsCaptureHeight, pageScreenshotCapture, initTransparentBackground, capturesAlpha, shouldDefaultCaptureBeyondViewport, } from "./screenshotService.js";
|
|
22
22
|
import { classifyGpuRenderer, detectGpuBackend, injectDrawElementCanvas, captureDrawElementFrame, resolveDrawElementCaptureMode, forcePreservedDrawingBuffer, instrumentAcceleratedCanvases, initDrawElementWorkerEncode, cleanupDrawElementWorkerEncode, produceDrawElementFrame, produceDrawElementFrameBatch, DE_CANVAS_NOT_INITIALIZED_CODE, } from "./drawElementService.js";
|
|
23
23
|
import { initThreeDProjection, scanCssEffects } from "./threeDProjection.js";
|
|
24
24
|
import { planRiskFrames, scanCssEffectLiveFrames, riskSessionFits, withFramesAfterCallback, MAX_RISK_FRAME_FRACTION, } from "./riskFrames.js";
|
|
@@ -3299,6 +3299,7 @@ async function captureFrameSurface(session, frameIndex, time, seekOptions, heldV
|
|
|
3299
3299
|
interval: session.beginFrameIntervalMs,
|
|
3300
3300
|
noDisplayUpdates: false,
|
|
3301
3301
|
});
|
|
3302
|
+
forgetBeginFrameCapture(page);
|
|
3302
3303
|
session.beginFrameSeekPresented = true;
|
|
3303
3304
|
}
|
|
3304
3305
|
const result = await beginFrameCapture(page, options, frameTimeTicks, session.beginFrameIntervalMs);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface InPageEncodeInput {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
captureMode: "drawelement" | "screenshot" | "beginframe";
|
|
4
|
+
format: string;
|
|
5
|
+
codec: string;
|
|
6
|
+
hdr: boolean;
|
|
7
|
+
transparent: boolean;
|
|
8
|
+
workers: number;
|
|
9
|
+
hls: boolean;
|
|
10
|
+
videoLayer: boolean;
|
|
11
|
+
encoderSupported: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type InPageEncodeDecision = {
|
|
14
|
+
eligible: true;
|
|
15
|
+
} | {
|
|
16
|
+
eligible: false;
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function resolveInPageEncode(input: InPageEncodeInput): InPageEncodeDecision;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function resolveInPageEncode(input) {
|
|
2
|
+
const blocker = [
|
|
3
|
+
[!input.enabled, "disabled"],
|
|
4
|
+
[input.captureMode !== "drawelement", "capture is not drawElement"],
|
|
5
|
+
[input.format !== "mp4", "output is not mp4"],
|
|
6
|
+
[input.codec !== "h264", "codec is not h264"],
|
|
7
|
+
[input.hdr, "hdr"],
|
|
8
|
+
[input.transparent, "transparent"],
|
|
9
|
+
[input.workers !== 1, "parallel workers"],
|
|
10
|
+
[input.hls, "hls"],
|
|
11
|
+
[input.videoLayer, "video layer passthrough"],
|
|
12
|
+
[!input.encoderSupported, "page encoder unsupported"],
|
|
13
|
+
].find(([blocked]) => blocked);
|
|
14
|
+
return blocker ? { eligible: false, reason: blocker[1] } : { eligible: true };
|
|
15
|
+
}
|
|
@@ -32,6 +32,7 @@ export interface BeginFrameResult {
|
|
|
32
32
|
* the safe direction (screenshot capture always works).
|
|
33
33
|
*/
|
|
34
34
|
export declare function probeBeginFrameLiveness(page: Page, timeoutMs: number, frameTimeTicks?: number, intervalMs?: number): Promise<boolean>;
|
|
35
|
+
export declare function forgetBeginFrameCapture(page: Page): void;
|
|
35
36
|
export declare function beginFrameCapture(page: Page, options: CaptureOptions, frameTimeTicks: number, interval: number): Promise<BeginFrameResult>;
|
|
36
37
|
/**
|
|
37
38
|
* True if the page's actual rendered content is taller than the requested
|
|
@@ -81,11 +81,14 @@ frameTimeTicks, intervalMs) {
|
|
|
81
81
|
* Requires chrome-headless-shell with --enable-begin-frame-control and
|
|
82
82
|
* --deterministic-mode flags.
|
|
83
83
|
*/
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
84
|
+
// The last buffer beginFrameCapture returned per page. A screenshot BeginFrame always
|
|
85
|
+
// reports damage, so a frame is reused only when a screenshot-less BeginFrame right after
|
|
86
|
+
// the seek reports none. Any BeginFrame sent elsewhere moves the display past this buffer:
|
|
87
|
+
// call forgetBeginFrameCapture first.
|
|
88
88
|
const lastFrameCache = new WeakMap();
|
|
89
|
+
export function forgetBeginFrameCapture(page) {
|
|
90
|
+
lastFrameCache.delete(page);
|
|
91
|
+
}
|
|
89
92
|
const PENDING_FRAME_RETRIES = 5;
|
|
90
93
|
async function sendBeginFrame(client, params) {
|
|
91
94
|
for (let attempt = 0;; attempt++) {
|
|
@@ -115,14 +118,30 @@ export async function beginFrameCapture(page, options, frameTimeTicks, interval)
|
|
|
115
118
|
quality: isPng ? undefined : (options.quality ?? 80),
|
|
116
119
|
optimizeForSpeed: true,
|
|
117
120
|
};
|
|
118
|
-
|
|
121
|
+
// With a cached buffer, a screenshot-less BeginFrame first draws the seek and reports
|
|
122
|
+
// whether it changed anything. The screenshot after it is not displayed: a displayed
|
|
123
|
+
// screenshot frame leaves damage the next probe would report, and a settle frame to
|
|
124
|
+
// absorb it would draw page changes that nothing captures.
|
|
125
|
+
const cached = lastFrameCache.get(page);
|
|
126
|
+
let screenshotTicks = frameTimeTicks;
|
|
127
|
+
if (cached) {
|
|
128
|
+
const probe = await sendBeginFrame(client, { frameTimeTicks, interval });
|
|
129
|
+
if (!probe.hasDamage)
|
|
130
|
+
return { buffer: cached, hasDamage: false };
|
|
131
|
+
screenshotTicks = frameTimeTicks + interval / 2;
|
|
132
|
+
}
|
|
133
|
+
const result = await sendBeginFrame(client, {
|
|
134
|
+
frameTimeTicks: screenshotTicks,
|
|
135
|
+
interval,
|
|
136
|
+
screenshot,
|
|
137
|
+
noDisplayUpdates: cached !== undefined,
|
|
138
|
+
});
|
|
119
139
|
let buffer;
|
|
120
140
|
if (result.screenshotData) {
|
|
121
141
|
buffer = Buffer.from(result.screenshotData, "base64");
|
|
122
142
|
lastFrameCache.set(page, buffer);
|
|
123
143
|
}
|
|
124
144
|
else {
|
|
125
|
-
const cached = lastFrameCache.get(page);
|
|
126
145
|
if (cached) {
|
|
127
146
|
buffer = cached;
|
|
128
147
|
}
|
|
@@ -109,3 +109,8 @@ export declare function buildStreamingArgs(options: StreamingEncoderOptions, out
|
|
|
109
109
|
* Spawn a streaming FFmpeg encoder that accepts frame buffers on stdin.
|
|
110
110
|
*/
|
|
111
111
|
export declare function spawnStreamingEncoder(outputPath: string, options: StreamingEncoderOptions, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegStreamingTimeout">>): Promise<StreamingEncoder>;
|
|
112
|
+
export declare function spawnFfmpegStream(outputPath: string, args: string[], { signal, config, checkFrame, }: {
|
|
113
|
+
signal?: AbortSignal;
|
|
114
|
+
config?: Partial<Pick<EngineConfig, "ffmpegStreamingTimeout">>;
|
|
115
|
+
checkFrame?: (frame: Buffer, index: number) => void;
|
|
116
|
+
}): StreamingEncoder;
|
|
@@ -302,15 +302,29 @@ export function buildStreamingArgs(options, outputPath, gpuEncoder = null, nvenc
|
|
|
302
302
|
* Spawn a streaming FFmpeg encoder that accepts frame buffers on stdin.
|
|
303
303
|
*/
|
|
304
304
|
export async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
305
|
-
const outputDir = dirname(outputPath);
|
|
306
|
-
if (!existsSync(outputDir))
|
|
307
|
-
mkdirSync(outputDir, { recursive: true });
|
|
308
305
|
let gpuEncoder = null;
|
|
309
306
|
if (options.useGpu) {
|
|
310
307
|
gpuEncoder = await getCachedGpuEncoder();
|
|
311
308
|
}
|
|
312
309
|
const nvencPresets = gpuEncoder === "nvenc" ? await getCachedNvencPresets() : undefined;
|
|
313
310
|
const args = buildStreamingArgs(options, outputPath, gpuEncoder, nvencPresets);
|
|
311
|
+
const checksJpeg = !options.rawInputFormat && (options.imageFormat ?? "jpeg") === "jpeg";
|
|
312
|
+
return spawnFfmpegStream(outputPath, args, {
|
|
313
|
+
signal,
|
|
314
|
+
config,
|
|
315
|
+
checkFrame: checksJpeg
|
|
316
|
+
? (frame, index) => {
|
|
317
|
+
const error = jpegInputError(frame);
|
|
318
|
+
if (error)
|
|
319
|
+
throw new Error(`Invalid JPEG input at frame ${index}: ${error}`);
|
|
320
|
+
}
|
|
321
|
+
: undefined,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
export function spawnFfmpegStream(outputPath, args, { signal, config, checkFrame, }) {
|
|
325
|
+
const outputDir = dirname(outputPath);
|
|
326
|
+
if (!existsSync(outputDir))
|
|
327
|
+
mkdirSync(outputDir, { recursive: true });
|
|
314
328
|
const ffmpeg = spawn(getFfmpegBinary(), args, {
|
|
315
329
|
stdio: ["pipe", "pipe", "pipe"],
|
|
316
330
|
// See runFfmpeg.ts: keeps a console window off the user's desktop on Windows.
|
|
@@ -412,11 +426,7 @@ export async function spawnStreamingEncoder(outputPath, options, signal, config)
|
|
|
412
426
|
// so without this copy the pipe would read partially-overwritten data
|
|
413
427
|
// and flicker.
|
|
414
428
|
const copy = Buffer.from(buffer);
|
|
415
|
-
|
|
416
|
-
const error = jpegInputError(copy);
|
|
417
|
-
if (error)
|
|
418
|
-
throw new Error(`Invalid JPEG input at frame ${inputFrameIndex}: ${error}`);
|
|
419
|
-
}
|
|
429
|
+
checkFrame?.(copy, inputFrameIndex);
|
|
420
430
|
const accepted = stdin.write(copy);
|
|
421
431
|
inputFrameIndex++;
|
|
422
432
|
// Reset inactivity timer immediately ONLY on `accepted === true`. `true`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"puppeteer": "~25.11.0",
|
|
60
60
|
"puppeteer-core": "^25.11.0",
|
|
61
61
|
"undici": "7.25.0",
|
|
62
|
-
"@hyperframes/
|
|
63
|
-
"@hyperframes/
|
|
62
|
+
"@hyperframes/core": "^0.9.6",
|
|
63
|
+
"@hyperframes/parsers": "^0.9.6"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^25.0.10",
|