@hyperframes/engine 0.8.196 → 0.8.198

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export { chromeMajorCeiling } from "./services/chromeHostCeiling.js";
38
38
  export { acquireBrowser, setHostHandlesSigint, releaseBrowser, drainBrowserPool, closeBrowserPool, resolveHeadlessShellPath, resolveBrowserGpuMode, buildChromeArgs, compositionRequiresWebGpu, assertWebGpuAdapterAvailable, usesSoftwareWebGpu, WebGpuUnavailableError, ENABLE_BROWSER_POOL, BrowserLeasePool, type GpuProbeLifecycle, type BuildChromeArgsOptions, type BrowserLaunchFingerprint, type BrowserLease, type BrowserPoolState, type CaptureMode, type AcquiredBrowser, } from "./services/browserManager.js";
39
39
  export { augmentProtocolTimeoutError, isProtocolTimeoutError, } from "./services/protocolTimeoutErrorHint.js";
40
40
  export { augmentPageNavigationTimeoutError, isPageNavigationTimeoutError, type NavigationTimeoutHintContext, } from "./services/pageNavigationTimeoutErrorHint.js";
41
- export { createCaptureSession, initializeSession, closeCaptureSession, captureFrame, captureFrameToBuffer, captureFrameToBufferPipelined, captureFramesBatchPipelined, DrawElementVerificationError, isDrawElementVerificationError, getDrawElementVerificationDetails, type DrawElementVerificationDetails, recaptureDrawElementFrameForVerify, completeDeferredDrawElementInit, writeCapturedFrame, discardWarmupCapture, getCompositionDuration, getCapturePerfSummary, percentileOf, prepareCaptureSessionForReuse, deriveBeginFrameProbeTimeTicks, type CaptureSession, isTransientBrowserError, isMemoryExhaustionError, type BeforeCaptureHook, type DiscardWarmupInnerCapture, type StaticVerificationOutcome, VfxFailureError, waitForImagesReady, } from "./services/frameCapture.js";
41
+ export { createCaptureSession, initializeSession, closeCaptureSession, captureFrame, captureFrameToBuffer, captureFrameToBufferPipelined, captureFramesBatchPipelined, DrawElementVerificationError, isDrawElementVerificationError, getDrawElementVerificationDetails, type DrawElementVerificationDetails, recaptureDrawElementFrameForVerify, completeDeferredDrawElementInit, writeCapturedFrame, discardWarmupCapture, getCompositionDuration, getCapturePerfSummary, percentileOf, prepareCaptureSessionForReuse, deriveBeginFrameProbeTimeTicks, type CaptureSession, isTransientBrowserError, isMemoryExhaustionError, type BeforeCaptureHook, type DiscardWarmupInnerCapture, type StaticVerificationOutcome, VfxFailureError, waitForImagesReady, evaluateHfDiagnostic, buildZeroDurationDiagnostic, } from "./services/frameCapture.js";
42
42
  export { CaptureFailure, classifyCaptureFailure, isFatalCaptureFailure, type CaptureFailureKind, type CaptureWorkerDiagnostic, } from "./services/captureFailure.js";
43
43
  export { createChromeMemorySampler, type ChromeMemoryStats, } from "./services/chromeMemorySampler.js";
44
44
  export { installMediaRenderIdBridge } from "./services/mediaRenderIdBridge.js";
@@ -46,7 +46,7 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
46
46
  export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, ENCODER_PRESETS, getEncoderPreset, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, type GpuEncoder, type PackageHlsOptions, } from "./services/chunkEncoder.js";
47
47
  export type { EncoderOptions, EncodeResult, MuxResult } from "./services/chunkEncoder.types.js";
48
48
  export { spawnStreamingEncoder, createFrameReorderBuffer, type StreamingEncoder, type StreamingEncoderOptions, type StreamingEncoderResult, type FrameReorderBuffer, } from "./services/streamingEncoder.js";
49
- export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveVideoExtractionDuration, resolvePlayableVideoDuration, 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, } from "./services/videoFrameExtractor.js";
49
+ export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, 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, } from "./services/videoFrameExtractor.js";
50
50
  export { directorySizeBytes, gcExtractionCache } from "./services/extractionCache.js";
51
51
  export { resolveReferencedStart, resolveReferencedDuration, type RefResolverEl, type RefResolverDoc, } from "./services/referenceResolver.js";
52
52
  export { createVideoFrameInjector } from "./services/videoFrameInjector.js";
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ export { acquireBrowser, setHostHandlesSigint, releaseBrowser, drainBrowserPool,
40
40
  export { augmentProtocolTimeoutError, isProtocolTimeoutError, } from "./services/protocolTimeoutErrorHint.js";
41
41
  export { augmentPageNavigationTimeoutError, isPageNavigationTimeoutError, } from "./services/pageNavigationTimeoutErrorHint.js";
42
42
  // ── Frame capture pipeline ──────────────────────────────────────────────────────
43
- export { createCaptureSession, initializeSession, closeCaptureSession, captureFrame, captureFrameToBuffer, captureFrameToBufferPipelined, captureFramesBatchPipelined, DrawElementVerificationError, isDrawElementVerificationError, getDrawElementVerificationDetails, recaptureDrawElementFrameForVerify, completeDeferredDrawElementInit, writeCapturedFrame, discardWarmupCapture, getCompositionDuration, getCapturePerfSummary, percentileOf, prepareCaptureSessionForReuse, deriveBeginFrameProbeTimeTicks, isTransientBrowserError, isMemoryExhaustionError, VfxFailureError, waitForImagesReady, } from "./services/frameCapture.js";
43
+ export { createCaptureSession, initializeSession, closeCaptureSession, captureFrame, captureFrameToBuffer, captureFrameToBufferPipelined, captureFramesBatchPipelined, DrawElementVerificationError, isDrawElementVerificationError, getDrawElementVerificationDetails, recaptureDrawElementFrameForVerify, completeDeferredDrawElementInit, writeCapturedFrame, discardWarmupCapture, getCompositionDuration, getCapturePerfSummary, percentileOf, prepareCaptureSessionForReuse, deriveBeginFrameProbeTimeTicks, isTransientBrowserError, isMemoryExhaustionError, VfxFailureError, waitForImagesReady, evaluateHfDiagnostic, buildZeroDurationDiagnostic, } from "./services/frameCapture.js";
44
44
  export { CaptureFailure, classifyCaptureFailure, isFatalCaptureFailure, } from "./services/captureFailure.js";
45
45
  export { createChromeMemorySampler, } from "./services/chromeMemorySampler.js";
46
46
  // ── Screenshot (BeginFrame) ─────────────────────────────────────────────────────
@@ -50,7 +50,7 @@ export { beginFrameCapture, pageScreenshotCapture, getCdpSession, injectVideoFra
50
50
  export { buildEncoderArgs, encodeFramesFromDir, buildConcatArgs, concatVideoFiles, encodeFramesChunkedConcat, muxVideoWithAudio, applyFaststart, packageHls, detectGpuEncoder, ENCODER_PRESETS, getEncoderPreset, HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, HLS_AUDIO_PLAYLIST, } from "./services/chunkEncoder.js";
51
51
  export { spawnStreamingEncoder, createFrameReorderBuffer, } from "./services/streamingEncoder.js";
52
52
  // ── Media processing ───────────────────────────────────────────────────────────
53
- export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveVideoExtractionDuration, resolvePlayableVideoDuration, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, } from "./services/videoFrameExtractor.js";
53
+ export { parseVideoElements, parseImageElements, extractVideoFramesRange, extractAllVideoFrames, isVideoPastTimelineEnd, resolveTimelineExtractionWindow, resolveVideoExtractionWindow, rebaseVideoToWindow, resolveFinalFrameExtractionWindow, resolveFrameFormat, resolveVideoExtractionDuration, resolvePlayableVideoDuration, extractionFrameCountForDuration, resolveProjectRelativeSrc, getFrameAtTime, isVideoHiddenBeforeStreamStart, createFrameLookupTable, FrameLookupTable, analyzeClipMediaFit, classifyVideoExtractionError, isVideoSourceExtractionError, runVideoExtractionWithRetry, safeVideoExtractionSourceIdentity, VideoSourceExtractionError, VIDEO_FRAME_FORMATS, isVideoFrameFormat, EXTRACT_CACHE_MIN_AGE_MS, } from "./services/videoFrameExtractor.js";
54
54
  export { directorySizeBytes, gcExtractionCache } from "./services/extractionCache.js";
55
55
  export { resolveReferencedStart, resolveReferencedDuration, } from "./services/referenceResolver.js";
56
56
  export { createVideoFrameInjector } from "./services/videoFrameInjector.js";
@@ -410,6 +410,7 @@ export interface HfDiagnostic {
410
410
  rejectedBuildReadyKeys: string[];
411
411
  }
412
412
  export declare function buildZeroDurationDiagnostic(diag: HfDiagnostic): string;
413
+ export declare function evaluateHfDiagnostic(page: Page): Promise<HfDiagnostic>;
413
414
  export declare function pollHfReady(page: Page, timeoutMs: number, intervalMs?: number): Promise<void>;
414
415
  export declare function waitForSubCompositionTimelines(session: CaptureSession, page: Page, timeoutMs: number, options?: {
415
416
  scriptErrorGraceMs?: number;
@@ -1131,7 +1131,7 @@ export function buildZeroDurationDiagnostic(diag) {
1131
1131
  `data-duration: ${diag.declaredDuration > 0 ? diag.declaredDuration + "s" : "not set"}\n` +
1132
1132
  (hints.length > 0 ? hints.map((h) => ` → ${h}`).join("\n") : ""));
1133
1133
  }
1134
- async function evaluateHfDiagnostic(page) {
1134
+ export async function evaluateHfDiagnostic(page) {
1135
1135
  return (await page.evaluate(HF_READY_DIAGNOSTIC_EXPR));
1136
1136
  }
1137
1137
  export async function pollHfReady(page, timeoutMs, intervalMs = 100) {
@@ -972,7 +972,7 @@ export function resolveVideoExtractionDuration(video, metadata, timelineEnd) {
972
972
  }
973
973
  /** Codecs whose bitstream can carry alpha: decoded with the alpha-aware decoder whether or not a tag says so. */
974
974
  const ALPHA_CAPABLE_CODECS = new Set(["vp9", "vp8", "prores"]);
975
- /** Their alpha rides a side channel ffprobe can miss, so frames stay PNG; ProRes alpha shows in its pixel format. */
975
+ /** Alpha can hide from ffprobe: PNG unless a decoded frame had no alpha; ProRes alpha shows in pix_fmt. */
976
976
  const SIDECAR_ALPHA_CODECS = new Set(["vp9", "vp8"]);
977
977
  export function codecMayHaveAlpha(codec) {
978
978
  return ALPHA_CAPABLE_CODECS.has((codec ?? "").toLowerCase());
@@ -981,7 +981,8 @@ export function decoderForCodec(codec) {
981
981
  return alphaDecoderFor(codec) ?? (codec ?? "").toLowerCase();
982
982
  }
983
983
  export function resolveFrameFormat(metadata, requested) {
984
- if (metadata.hasAlpha || SIDECAR_ALPHA_CODECS.has((metadata.videoCodec ?? "").toLowerCase()))
984
+ const sidecar = SIDECAR_ALPHA_CODECS.has((metadata.videoCodec ?? "").toLowerCase());
985
+ if (metadata.hasAlpha || (sidecar && metadata.alphaPlaneDecoded !== false))
985
986
  return "png";
986
987
  if (requested === "png" || requested === "jpg")
987
988
  return requested;
@@ -29,6 +29,9 @@ export interface VideoMetadata {
29
29
  isVFR: boolean;
30
30
  /** True when the stream carries an alpha channel. */
31
31
  hasAlpha: boolean;
32
+ /** VP8/VP9 without an alpha tag: whether a frame decoded with the alpha-aware decoder had an alpha plane.
33
+ * Absent when not probed or the probe failed. */
34
+ alphaPlaneDecoded?: boolean;
32
35
  /** Color space info from the video stream. Null if ffprobe didn't report it. */
33
36
  colorSpace: VideoColorSpace | null;
34
37
  colorRange?: string;
@@ -155,6 +158,11 @@ export declare function readTagCI(tags: Record<string, string | undefined> | und
155
158
  * smallest representable 2dp rate instead.
156
159
  */
157
160
  export declare function parseFrameRate(frameRateStr: string | undefined): number;
161
+ /**
162
+ * Whether the first frame decoded with `decoder` (alpha-aware) has an alpha pixel format; undefined on any
163
+ * failure. ffprobe cannot pick a decoder, so ffmpeg writes the frame as rawvideo, which keeps its pix_fmt.
164
+ */
165
+ export declare function probeDecodedAlphaPlane(filePath: string, decoder: string): Promise<boolean | undefined>;
158
166
  /**
159
167
  * Probe a media file (video, image, or container) and return normalized metadata.
160
168
  *
@@ -1,12 +1,14 @@
1
1
  // fallow-ignore-file code-duplication complexity
2
2
  import { spawn } from "child_process";
3
- import { createReadStream, readFileSync, statSync } from "fs";
3
+ import { createReadStream, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
4
4
  import { chunkCrc32 } from "./crc32.js";
5
5
  import { StringDecoder } from "node:string_decoder";
6
- import { basename } from "path";
6
+ import { tmpdir } from "os";
7
+ import { basename, join } from "path";
7
8
  import { redactTelemetryString } from "@hyperframes/core";
8
- import { videoStreamHasAlpha } from "@hyperframes/parsers/media-alpha";
9
+ import { alphaDecoderFor, pixelFormatHasAlpha, videoStreamHasAlpha, } from "@hyperframes/parsers/media-alpha";
9
10
  import { urlInputArgs } from "./urlInput.js";
11
+ import { runFfmpeg } from "./runFfmpeg.js";
10
12
  import { FFPROBE_PATH_ENV, getFfprobeBinary } from "./ffmpegBinaries.js";
11
13
  import { ManagedChildProcess } from "./managedChildProcess.js";
12
14
  import { trackChildProcess } from "./processTracker.js";
@@ -617,6 +619,54 @@ export function parseFrameRate(frameRateStr) {
617
619
  // caller's 30fps default.
618
620
  return rounded > 0 ? rounded : 0.01;
619
621
  }
622
+ /**
623
+ * Whether the first frame decoded with `decoder` (alpha-aware) has an alpha pixel format; undefined on any
624
+ * failure. ffprobe cannot pick a decoder, so ffmpeg writes the frame as rawvideo, which keeps its pix_fmt.
625
+ */
626
+ export async function probeDecodedAlphaPlane(filePath, decoder) {
627
+ let probeDir;
628
+ try {
629
+ probeDir = mkdtempSync(join(tmpdir(), "hf-decoded-alpha-"));
630
+ const framePath = join(probeDir, "frame.nut");
631
+ const decoded = await runFfmpeg([
632
+ "-v",
633
+ "error",
634
+ ...urlInputArgs(filePath),
635
+ "-c:v",
636
+ decoder,
637
+ "-i",
638
+ filePath,
639
+ "-frames:v",
640
+ "1",
641
+ "-vf",
642
+ "scale=8:8",
643
+ "-c:v",
644
+ "rawvideo",
645
+ "-f",
646
+ "nut",
647
+ framePath,
648
+ ], { timeout: 30_000 });
649
+ if (!decoded.success)
650
+ return undefined;
651
+ const stdout = await runFfprobe(framePath, [
652
+ "-select_streams",
653
+ "v:0",
654
+ "-show_entries",
655
+ "stream=pix_fmt",
656
+ "-of",
657
+ "json",
658
+ ]);
659
+ const pixFmt = JSON.parse(stdout).streams?.[0]?.pix_fmt;
660
+ return pixFmt ? pixelFormatHasAlpha(pixFmt) : undefined;
661
+ }
662
+ catch {
663
+ return undefined;
664
+ }
665
+ finally {
666
+ if (probeDir)
667
+ rmSync(probeDir, { recursive: true, force: true });
668
+ }
669
+ }
620
670
  /**
621
671
  * Probe a media file (video, image, or container) and return normalized metadata.
622
672
  *
@@ -697,6 +747,10 @@ export async function extractMediaMetadata(filePath) {
697
747
  const colorSpace = merged.colorTransfer || merged.colorPrimaries || merged.colorSpace ? merged : null;
698
748
  const pixelFormat = videoStream.pix_fmt || "";
699
749
  const hasAlpha = videoStreamHasAlpha(videoStream);
750
+ const alphaDecoder = hasAlpha ? null : alphaDecoderFor(videoStream.codec_name);
751
+ const alphaPlaneDecoded = alphaDecoder
752
+ ? await probeDecodedAlphaPlane(filePath, alphaDecoder)
753
+ : undefined;
700
754
  const containerDuration = output?.format.duration ? parseFloat(output.format.duration) : 0;
701
755
  const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
702
756
  const parsedStreamStart = videoStream.start_time ? parseFloat(videoStream.start_time) : 0;
@@ -739,6 +793,7 @@ export async function extractMediaMetadata(filePath) {
739
793
  hasAudio: output?.streams.some((s) => s.codec_type === "audio") ?? false,
740
794
  isVFR,
741
795
  hasAlpha,
796
+ ...(alphaPlaneDecoded === undefined ? {} : { alphaPlaneDecoded }),
742
797
  colorSpace,
743
798
  colorRange: videoStream.color_range,
744
799
  pixelFormat,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.196",
3
+ "version": "0.8.198",
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/parsers": "^0.8.196",
54
- "@hyperframes/core": "^0.8.196"
53
+ "@hyperframes/parsers": "^0.8.198",
54
+ "@hyperframes/core": "^0.8.198"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",