@hyperframes/producer 0.8.175 → 0.8.177

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.
@@ -13763,13 +13763,42 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
13763
13763
  // src/services/distributed/plan.ts
13764
13764
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
13765
13765
  import { extname as extname4, isAbsolute as isAbsolute4, join as join29, relative as relative4, sep as sep3 } from "node:path";
13766
- import { fpsToNumber as fpsToNumber10 } from "@hyperframes/core";
13766
+ import {
13767
+ fpsToNumber as fpsToNumber10,
13768
+ HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
13769
+ } from "@hyperframes/core";
13767
13770
  import {
13768
13771
  getEncoderPreset as getEncoderPreset4,
13769
13772
  normalizeVp9CpuUsed,
13770
13773
  resolveConfig as resolveConfig3
13771
13774
  } from "@hyperframes/engine";
13772
13775
 
13776
+ // src/services/distributed/planFrameCost.ts
13777
+ import {
13778
+ closeCaptureSession as closeCaptureSession9,
13779
+ createCaptureSession as createCaptureSession8,
13780
+ createFrameLookupTable as createFrameLookupTable3,
13781
+ initializeSession as initializeSession8
13782
+ } from "@hyperframes/engine";
13783
+
13784
+ // src/services/distributed/chunkFrames.ts
13785
+ import {
13786
+ compositionRequiresWebGpu as compositionRequiresWebGpu3,
13787
+ createFrameLookupTable as createFrameLookupTable2,
13788
+ createVideoFrameInjector as createVideoFrameInjector2,
13789
+ extractVideoFramesRange
13790
+ } from "@hyperframes/engine";
13791
+
13792
+ // src/services/distributed/shared.ts
13793
+ import { execFile as execFileCallback } from "node:child_process";
13794
+ import { promisify as promisify2 } from "node:util";
13795
+ import {
13796
+ getFfmpegBinary,
13797
+ MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME3
13798
+ } from "@hyperframes/engine";
13799
+ var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME3;
13800
+ var execFile = promisify2(execFileCallback);
13801
+
13773
13802
  // src/services/distributed/planProtocol.ts
13774
13803
  var PLAN_SCHEMA_VERSION = 1;
13775
13804
  var PLAN_ARTIFACT_LAYOUT = "plan-dir-v1";
@@ -13806,16 +13835,6 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
13806
13835
  // src/services/render/stages/planHash.ts
13807
13836
  var FIELD_DELIMITER = Buffer.from([0]);
13808
13837
 
13809
- // src/services/distributed/shared.ts
13810
- import { execFile as execFileCallback } from "node:child_process";
13811
- import { promisify as promisify2 } from "node:util";
13812
- import {
13813
- getFfmpegBinary,
13814
- MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME3
13815
- } from "@hyperframes/engine";
13816
- var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME3;
13817
- var execFile = promisify2(execFileCallback);
13818
-
13819
13838
  // src/services/distributed/planSize.ts
13820
13839
  var PLAN_ROOT_CATEGORY = {
13821
13840
  compiled: "compiled",
@@ -0,0 +1,48 @@
1
+ import { type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type EngineConfig, type ExtractedFrames, type FrameLookupTable } from "@hyperframes/engine";
2
+ import type { LockedRenderConfig } from "../render/stages/freezePlan.js";
3
+ import { type DistributedFormat, INVALID_VIDEO_METADATA, type PlanVideosJson } from "./shared.js";
4
+ /** Distinct codes so adapters route retries per cause: a missing artifact may heal, a hash mismatch never. */
5
+ export declare const FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
6
+ export declare const PLAN_HASH_MISMATCH = "PLAN_HASH_MISMATCH";
7
+ export declare const MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
8
+ export declare const CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
9
+ export declare const MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
10
+ export { INVALID_VIDEO_METADATA };
11
+ export type RenderChunkValidationCode = typeof FFMPEG_VERSION_MISMATCH | typeof PLAN_HASH_MISMATCH | typeof MISSING_PLAN_ARTIFACT | typeof CHUNK_INDEX_OUT_OF_RANGE | typeof MISSING_RUNTIME_ENV_SNAPSHOT | typeof INVALID_VIDEO_METADATA | typeof BROWSER_GPU_NOT_SOFTWARE;
12
+ /** A malformed planDir or a worker that does not match the plan's fingerprint; adapters key retries off `code`. */
13
+ export declare class RenderChunkValidationError extends Error {
14
+ readonly code: RenderChunkValidationCode;
15
+ constructor(code: RenderChunkValidationCode, message: string);
16
+ }
17
+ /** One injector per browser session: a hook reused on a fresh page can skip its first injection. */
18
+ export declare function createChunkVideoFrameInjectorFactory(frameLookup: FrameLookupTable | null): () => BeforeCaptureHook | null;
19
+ /**
20
+ * Rebuilds `ExtractedFrames[]` from `<planDir>/video-frames/<videoId>/`. `framePaths` keys are 0-based
21
+ * frame indexes (what `getFrameAtTime` reads); any other key drops every video's first-paint frame.
22
+ */
23
+ export declare function rebuildExtractedFramesFromPlanDir(planDir: string, videos: PlanVideosJson["extracted"], indexMode?: "dense-v1" | "sparse-v2"): ExtractedFrames[];
24
+ /** Capture options a chunk session uses; the planner measures frame cost with the same ones. */
25
+ export declare function buildChunkCaptureOptions(input: {
26
+ dimensions: {
27
+ width: number;
28
+ height: number;
29
+ fpsNum: number;
30
+ fpsDen: number;
31
+ format: DistributedFormat;
32
+ };
33
+ encoder: Pick<LockedRenderConfig, "quality" | "deviceScaleFactor" | "variables">;
34
+ videoCount: number;
35
+ html: string;
36
+ }): CaptureOptions;
37
+ /** Extracts the given frames of deferred videos under the names a full extraction gives them. */
38
+ export declare function extractDeferredVideoFramesForChunk(input: {
39
+ planDir: string;
40
+ planVideos: PlanVideosJson;
41
+ frames: Iterable<number>;
42
+ fps: {
43
+ num: number;
44
+ den: number;
45
+ };
46
+ outputRoot: string;
47
+ cfg: Pick<EngineConfig, "ffmpegProcessTimeout">;
48
+ }): Promise<ExtractedFrames[]>;
@@ -261,7 +261,11 @@ export declare const MIN_CHUNK_SIZE = 10;
261
261
  /** Most chunks one render may plan; `validateDistributedRenderConfig` enforces it on `maxParallelChunks`. */
262
262
  export declare const MAX_PARALLEL_CHUNKS_CEILING = 256;
263
263
  /** Predicted seconds for one chunk of `frames` frames at `megapixels` output pixels per frame. */
264
- export declare function estimateChunkSeconds(frames: number, megapixels: number): number;
264
+ export declare function estimateChunkSeconds(frames: number, megapixels: number, measuredSecondsPerFrame?: number): number;
265
+ /** Graded media runs a full-frame shader per frame, which the per-pixel estimate cannot see. */
266
+ export declare function planNeedsFrameCostMeasurement(html: string): boolean;
267
+ /** Whether {@link resolveMaxParallelChunks} sizes chunks from what a frame costs. */
268
+ export declare function chunkSizingUsesFrameCost(config: Pick<DistributedRenderConfig, "maxParallelChunks" | "chunkSize" | "targetChunkFrames" | "workerTimeoutSeconds" | "workerConcurrency">): boolean;
265
269
  /** Non-retryable: even {@link MAX_PARALLEL_CHUNKS_CEILING} chunks cannot fit the worker time limit. */
266
270
  export declare const CHUNK_EXCEEDS_WORKER_TIMEOUT = "CHUNK_EXCEEDS_WORKER_TIMEOUT";
267
271
  export declare class ChunkExceedsWorkerTimeoutError extends Error {
@@ -274,7 +278,9 @@ export declare class ChunkExceedsWorkerTimeoutError extends Error {
274
278
  */
275
279
  export declare function resolveMaxParallelChunks(config: Pick<DistributedRenderConfig, "maxParallelChunks" | "chunkSize" | "targetChunkFrames" | "workerTimeoutSeconds" | "workerConcurrency">, totalFrames: number,
276
280
  /** Output pixels per frame, after supersampling. */
277
- megapixels: number, log?: ProducerLogger): number;
281
+ megapixels: number, log?: ProducerLogger,
282
+ /** Measured capture seconds per frame; frames are priced at it when it beats the estimate. */
283
+ measuredSecondsPerFrame?: number): number;
278
284
  /**
279
285
  * Default hard ceiling on `<planDir>/` size in bytes. 2 GB fits inside
280
286
  * AWS Lambda's 10 GB `/tmp` alongside the chunk worker's captured frames
@@ -0,0 +1,24 @@
1
+ import { type EngineConfig } from "@hyperframes/engine";
2
+ import type { ProducerLogger } from "../../logger.js";
3
+ import type { DistributedFormat, PlanVideosJson } from "./shared.js";
4
+ /**
5
+ * Captures the renderer's calibration frames in a session set up like a chunk
6
+ * worker's (same capture options, engine config and video frames) and returns
7
+ * the p95 capture seconds per frame.
8
+ */
9
+ export declare function measurePlanSecondsPerFrame(input: {
10
+ planDir: string;
11
+ workDir: string;
12
+ html: string;
13
+ planVideos: PlanVideosJson;
14
+ totalFrames: number;
15
+ fps: number;
16
+ width: number;
17
+ height: number;
18
+ format: DistributedFormat;
19
+ quality: "draft" | "standard" | "high";
20
+ deviceScaleFactor: number;
21
+ variables?: Record<string, unknown>;
22
+ cfg: EngineConfig;
23
+ log: ProducerLogger;
24
+ }): Promise<number>;
@@ -33,35 +33,10 @@
33
33
  * Every determinism toggle above is opt-in — only this primitive enables them.
34
34
  * In-process renders (`executeRenderJob`) leave them off.
35
35
  */
36
- import { assertSwiftShader, type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type CaptureMode, type CaptureSession, closeCaptureSession, createCaptureSession, type EngineConfig, type ExtractedFrames, type FrameLookupTable, initializeSession, probeBeginFrameLiveness, readWebGlVendorInfoFromCanvas } from "@hyperframes/engine";
37
- import { type ChunkSliceJson, type LockedRenderConfig } from "../render/stages/freezePlan.js";
38
- import { INVALID_VIDEO_METADATA, type PlanVideosJson } from "./shared.js";
39
- /**
40
- * Non-retryable error codes raised when the planDir is structurally
41
- * malformed, semantically out of range, or fingerprints differently from
42
- * what the controller wrote. Each is distinct so adapter retry policies
43
- * can route them independently — e.g. `MISSING_PLAN_ARTIFACT` may point
44
- * to a partial S3 download that a retry could heal, while
45
- * `PLAN_HASH_MISMATCH` strictly indicates cross-version drift that
46
- * retries won't fix.
47
- */
48
- export declare const FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
49
- export declare const PLAN_HASH_MISMATCH = "PLAN_HASH_MISMATCH";
50
- export declare const MISSING_PLAN_ARTIFACT = "MISSING_PLAN_ARTIFACT";
51
- export declare const CHUNK_INDEX_OUT_OF_RANGE = "CHUNK_INDEX_OUT_OF_RANGE";
52
- export declare const MISSING_RUNTIME_ENV_SNAPSHOT = "MISSING_RUNTIME_ENV_SNAPSHOT";
53
- export { INVALID_VIDEO_METADATA };
54
- export type RenderChunkValidationCode = typeof FFMPEG_VERSION_MISMATCH | typeof PLAN_HASH_MISMATCH | typeof MISSING_PLAN_ARTIFACT | typeof CHUNK_INDEX_OUT_OF_RANGE | typeof MISSING_RUNTIME_ENV_SNAPSHOT | typeof INVALID_VIDEO_METADATA | typeof BROWSER_GPU_NOT_SOFTWARE;
55
- /**
56
- * Typed non-retryable error raised by `renderChunk` when the planDir is
57
- * malformed or the worker's runtime doesn't match the planDir's
58
- * controller-side fingerprint. Workflow adapters key retry policies off
59
- * `code` — most of these failures will not heal on retry.
60
- */
61
- export declare class RenderChunkValidationError extends Error {
62
- readonly code: RenderChunkValidationCode;
63
- constructor(code: RenderChunkValidationCode, message: string);
64
- }
36
+ import { assertSwiftShader, type CaptureOptions, type CaptureMode, type CaptureSession, closeCaptureSession, createCaptureSession, type EngineConfig, initializeSession, probeBeginFrameLiveness, readWebGlVendorInfoFromCanvas } from "@hyperframes/engine";
37
+ import { type LockedRenderConfig } from "../render/stages/freezePlan.js";
38
+ import { type PlanVideosJson } from "./shared.js";
39
+ export { CHUNK_INDEX_OUT_OF_RANGE, createChunkVideoFrameInjectorFactory, extractDeferredVideoFramesForChunk, FFMPEG_VERSION_MISMATCH, INVALID_VIDEO_METADATA, MISSING_PLAN_ARTIFACT, MISSING_RUNTIME_ENV_SNAPSHOT, PLAN_HASH_MISMATCH, rebuildExtractedFramesFromPlanDir, RenderChunkValidationError, } from "./chunkFrames.js";
65
40
  /** Validate the shared video contract before any v1 chunk can inject frames. */
66
41
  export declare function validatePlanVideosForChunk(value: unknown): PlanVideosJson;
67
42
  /**
@@ -134,12 +109,6 @@ export declare const distributedCaptureSessionDependencies: DistributedCaptureSe
134
109
  * assertion, including fresh screenshot browsers created after a fallback.
135
110
  */
136
111
  export declare function createVerifiedDistributedCaptureSession(serverUrl: string, framesDir: string, captureOptions: CaptureOptions, cfg: EngineConfig, dependencies?: DistributedCaptureSessionDependencies): Promise<CaptureSession>;
137
- /**
138
- * Build the immutable lookup table once, but keep injector state scoped to a
139
- * browser session. Reusing one hook across workers or a whole-chunk retry can
140
- * incorrectly suppress injection on a fresh page.
141
- */
142
- export declare function createChunkVideoFrameInjectorFactory(frameLookup: FrameLookupTable | null): () => BeforeCaptureHook | null;
143
112
  /**
144
113
  * Only BeginFrame-specific failures are safe to retry in screenshot mode.
145
114
  * Cancellation, memory exhaustion, and unrelated authoring/IO failures must
@@ -161,40 +130,6 @@ export declare function runCaptureWithScreenshotFallback<T>(input: {
161
130
  * and frame zero. `true` means the entire chunk should use screenshot mode.
162
131
  */
163
132
  export declare function beginFrameSessionNeedsScreenshotFallback(session: Pick<CaptureSession, "page" | "launchCaptureMode" | "beginFrameTimeTicks" | "beginFrameIntervalMs">, probe?: typeof probeBeginFrameLiveness): Promise<boolean>;
164
- /**
165
- * Rebuild the engine's in-memory `ExtractedFrames[]` from the on-disk
166
- * planDir layout. `<planDir>/video-frames/<videoId>/` holds the numbered
167
- * frame files plan() extracted; this lists each dir and rebuilds the
168
- * 0-based `framePaths` Map that `FrameLookupTable` / `videoFrameInjector`
169
- * both index against — the consumer is
170
- * `videoFrameExtractor.ts:getFrameAtTime`, which floors `localTime * fps`
171
- * to a 0-based index and reads `framePaths.get(frameIndex)`. Any drift
172
- * from that key convention silently drops every `<video>`'s first-paint
173
- * frame; see HF#1731 / HF#1730.
174
- *
175
- * Exported so a unit test can pin the 0-based contract without spinning
176
- * up the heavyweight Docker fixture — the bug surfaces only under
177
- * distributed mode and only at video first-paint, so this primitive is
178
- * the right granularity to guard.
179
- */
180
- export declare function rebuildExtractedFramesFromPlanDir(planDir: string, videos: PlanVideosJson["extracted"], indexMode?: "dense-v1" | "sparse-v2"): ExtractedFrames[];
181
- /**
182
- * Extract the frames this chunk shows from videos the planner left
183
- * unextracted. Frames land in `outputRoot/<videoId>/` under the names a full
184
- * extraction gives them, so the lookup indexes them exactly as it would
185
- * frames the planner wrote.
186
- */
187
- export declare function extractDeferredVideoFramesForChunk(input: {
188
- planDir: string;
189
- planVideos: PlanVideosJson;
190
- slice: ChunkSliceJson;
191
- fps: {
192
- num: number;
193
- den: number;
194
- };
195
- outputRoot: string;
196
- cfg: Pick<EngineConfig, "ffmpegProcessTimeout">;
197
- }): Promise<ExtractedFrames[]>;
198
133
  /**
199
134
  * Re-export the runtime-env apply helper so adapters that import only
200
135
  * this subpath can prime `process.env` before instantiating their own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.175",
3
+ "version": "0.8.177",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/core": "0.8.175",
39
- "@hyperframes/engine": "0.8.175",
40
- "@hyperframes/parsers": "0.8.175",
41
- "@hyperframes/studio-server": "0.8.175",
42
- "@hyperframes/lint": "0.8.175"
38
+ "@hyperframes/engine": "0.8.177",
39
+ "@hyperframes/lint": "0.8.177",
40
+ "@hyperframes/parsers": "0.8.177",
41
+ "@hyperframes/studio-server": "0.8.177",
42
+ "@hyperframes/core": "0.8.177"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.175"
51
+ "@hyperframes/aws-lambda": "0.8.177"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"