@hyperframes/producer 0.8.176 → 0.8.178

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.
@@ -1,5 +1,6 @@
1
1
  import { type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type EngineConfig, type ExtractedFrames, type FrameLookupTable } from "@hyperframes/engine";
2
2
  import type { LockedRenderConfig } from "../render/stages/freezePlan.js";
3
+ import type { PlanV2RemoteSources } from "./planV2.js";
3
4
  import { type DistributedFormat, INVALID_VIDEO_METADATA, type PlanVideosJson } from "./shared.js";
4
5
  /** Distinct codes so adapters route retries per cause: a missing artifact may heal, a hash mismatch never. */
5
6
  export declare const FFMPEG_VERSION_MISMATCH = "FFMPEG_VERSION_MISMATCH";
@@ -33,6 +34,8 @@ export declare function buildChunkCaptureOptions(input: {
33
34
  encoder: Pick<LockedRenderConfig, "quality" | "deviceScaleFactor" | "variables">;
34
35
  videoCount: number;
35
36
  html: string;
37
+ skipReadinessVideoIds?: string[];
38
+ videoMetadataHints?: CaptureOptions["videoMetadataHints"];
36
39
  }): CaptureOptions;
37
40
  /** Extracts the given frames of deferred videos under the names a full extraction gives them. */
38
41
  export declare function extractDeferredVideoFramesForChunk(input: {
@@ -45,4 +48,5 @@ export declare function extractDeferredVideoFramesForChunk(input: {
45
48
  };
46
49
  outputRoot: string;
47
50
  cfg: Pick<EngineConfig, "ffmpegProcessTimeout">;
51
+ remoteSources?: PlanV2RemoteSources;
48
52
  }): Promise<ExtractedFrames[]>;
@@ -201,16 +201,20 @@ export interface BuildLocalExecutionPlanOptions {
201
201
  * chunks write those frames into their own work directory.
202
202
  */
203
203
  readonly deferVideoExtraction?: boolean;
204
+ /** Project files absent locally: probe their URL and publish their existing digest. */
205
+ readonly remoteFiles?: Readonly<Record<string, RemoteProjectFile>>;
204
206
  /** Shared with the publisher so each plan file is hashed once. */
205
207
  readonly digests?: PlanFileDigests;
206
208
  /** Runs while the audio mixes, once `compiled/` and the video trees are final. */
207
209
  readonly whileAudioMixes?: () => Promise<void>;
208
- /**
209
- * mp4 chunks carry their own slice of the plan audio (renderChunk muxes it). Ignored for other
210
- * formats and with `cfr`, which re-encodes.
211
- */
210
+ /** mp4 chunks carry their plan-audio slice unless cfr re-encodes. */
212
211
  readonly chunksCarryAudio?: boolean;
213
212
  }
213
+ export interface RemoteProjectFile {
214
+ readonly url: string;
215
+ readonly sha256: string;
216
+ readonly sizeBytes: number;
217
+ }
214
218
  export declare function logPlanStage(log: ProducerLogger, stage: string, startedAt: number): void;
215
219
  /**
216
220
  * Result of the legacy v1 {@link plan} wrapper. The `planHash` is the
@@ -11,7 +11,7 @@
11
11
  * is absent, v2 explicitly falls back to the full-source pack.
12
12
  */
13
13
  import { type DistributedFps } from "@hyperframes/core";
14
- import { type DistributedRenderConfig } from "./plan.js";
14
+ import { type BuildLocalExecutionPlanOptions, type DistributedRenderConfig } from "./plan.js";
15
15
  import { type PlanProtocolV2Descriptor } from "./planProtocol.js";
16
16
  import { type PlanV2ArtifactPublisher } from "./planV2Publisher.js";
17
17
  import { PLAN_V2_INTEGRITY_UNRECOVERABLE, PlanV2IntegrityError } from "./planV2Errors.js";
@@ -88,6 +88,8 @@ export interface PlanV2WithPublisherOptions {
88
88
  * can avoid a second set of data blocks.
89
89
  */
90
90
  readonly stagingParentDir?: string;
91
+ /** See {@link BuildLocalExecutionPlanOptions.remoteFiles}; needs a publisher with `referenceStoredBlob`. */
92
+ readonly remoteFiles?: BuildLocalExecutionPlanOptions["remoteFiles"];
91
93
  readonly chunksCarryAudio?: boolean;
92
94
  }
93
95
  export interface PlanV2MaterializationResult {
@@ -105,6 +107,13 @@ export interface PlanV2MaterializationResult {
105
107
  readonly sizeBytes: number;
106
108
  readonly audioPath: string | null;
107
109
  }
110
+ export type PlanV2RemoteSources = Readonly<Record<string, string>>;
111
+ export interface PlanV2MaterializeOptions {
112
+ /** Deferred video sources the chunk reads by URL; the caller vouches for their bytes. */
113
+ readonly remoteSources?: PlanV2RemoteSources;
114
+ }
115
+ /** A chunk-scoped compiled/ file is a deferred video source (see `artifactTargets`). */
116
+ export declare function isPlanV2DeferredSourceArtifact(artifact: Readonly<PlanV2Artifact>): boolean;
108
117
  /** Publish a frozen local execution directory into an immutable local v2 transport. */
109
118
  export declare function createPlanV2FromExecutionPlan(executionPlanDir: string, planV2Dir: string): PlanV2Result;
110
119
  /**
@@ -147,6 +156,10 @@ export declare function listPlanV2ArtifactsForTarget(manifest: Readonly<PlanV2Ma
147
156
  * execution directory. The marker lets execution functions revalidate the
148
157
  * selected subset without requiring assembler-only audio in chunk workers.
149
158
  */
150
- export declare function materializePlanV2Target(planV2Dir: string, target: PlanV2MaterializationTarget, destinationDir: string): PlanV2MaterializationResult;
159
+ export declare function materializePlanV2Target(planV2Dir: string, target: PlanV2MaterializationTarget, destinationDir: string, options?: Readonly<PlanV2MaterializeOptions>): PlanV2MaterializationResult;
151
160
  /** Revalidate a materialized subset immediately before execution. */
152
161
  export declare function validatePlanV2MaterializedTarget(planDir: string, expectedTarget: PlanV2MaterializationTarget): Readonly<PlanV2Manifest> | null;
162
+ export declare function readValidatedPlanV2Materialization(planDir: string, expectedTarget: PlanV2MaterializationTarget): {
163
+ manifest: Readonly<PlanV2Manifest>;
164
+ remoteSources: PlanV2RemoteSources;
165
+ } | null;
@@ -11,6 +11,7 @@ export interface PlanV2ArtifactPublisher {
11
11
  * adapters must finish reading/uploading it before the promise resolves.
12
12
  */
13
13
  putBlob(blob: Readonly<PlanV2PublishBlob>): Promise<void>;
14
+ referenceStoredBlob?(blob: Readonly<Omit<PlanV2PublishBlob, "sourcePath">>): Promise<void>;
14
15
  /** Commit the manifest only after every referenced blob is durable. */
15
16
  commitManifest(manifestBytes: string): Promise<void>;
16
17
  /** Idempotent best-effort cleanup for an unpublished or partial plan. */
@@ -2,24 +2,29 @@
2
2
  * Content-addressing for a project directory, shared by the distributed-render
3
3
  * adapters' `deploySite` verbs.
4
4
  *
5
- * Each adapter uploads a project tarball to `…/sites/<siteId>/project.tar.gz`
6
- * and short-circuits the upload when the object already exists. `siteId` is a
5
+ * Each adapter uploads a project under `…/sites/<siteId>/` (AWS: a file list,
6
+ * Cloud Run: a tarball) and skips content already uploaded. `siteId` is a
7
7
  * SHA-256 over the project's files so identical content always maps to the
8
8
  * same key — that contract has to be byte-identical across adapters, which is
9
9
  * exactly why it lives here rather than being copy-pasted per adapter.
10
10
  */
11
11
  /**
12
- * SHA-256 over every regular file under `projectDir` (sorted by relative
13
- * path) → 16-character hex prefix. The prefix is the `siteId`.
12
+ * The files a site upload ships, as `/`-separated paths under `projectDir`. Symlinks are followed
13
+ * (root included), top-level {@link PLAN_PROJECT_DIR_SKIP_SEGMENTS} are left out as the plan stage
14
+ * does, and a dangling symlink throws.
15
+ */
16
+ export declare function listSiteFiles(projectDir: string): string[];
17
+ /**
18
+ * SHA-256 over a scheme tag plus each of {@link listSiteFiles}'s paths and
19
+ * bytes → 16-character hex prefix. The prefix is the `siteId`.
14
20
  *
15
21
  * The hash includes the relative path plus every byte of each file, so a
16
22
  * same-bytes rename still yields a fresh id. We trim to 16 chars because the
17
- * full 64 isn't useful in an object key for legibility. Top-level segments in
18
- * {@link PLAN_PROJECT_DIR_SKIP_SEGMENTS} (e.g. `node_modules`) are skipped to
19
- * match what the plan stage copies.
23
+ * full 64 isn't useful in an object key for legibility. Pass `files` when the
24
+ * caller already listed them, so the hashed set is the shipped set.
20
25
  *
21
26
  * Reads are synchronous: project trees are typically tens of MB at most
22
27
  * (HTML/CSS/JS plus a few composition assets), so the simpler shape wins over
23
28
  * a streaming pipeline.
24
29
  */
25
- export declare function hashProjectDir(projectDir: string): string;
30
+ export declare function hashProjectDir(projectDir: string, files?: readonly string[]): string;
@@ -157,6 +157,8 @@ export interface CompileForRenderOptions {
157
157
  * mismatch warning). Optional so non-render callers can omit it.
158
158
  */
159
159
  log?: ProducerLogger;
160
+ /** Absolute project paths not on disk, readable at these URLs instead. */
161
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
160
162
  /**
161
163
  * Threaded through to {@link injectDeterministicFontFaces}. When `true`,
162
164
  * deterministic font resolution and exhausted transient fetch failures
@@ -33,6 +33,7 @@ export interface AudioStageInput {
33
33
  /** Read-only view of `composition.audios`. */
34
34
  audios: CompositionMetadata["audios"];
35
35
  masterFxChain?: string;
36
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
36
37
  abortSignal: AbortSignal | undefined;
37
38
  assertNotAborted: () => void;
38
39
  /** Where a per-track failure's detail goes. Optional so tests need not pass one. */
@@ -78,6 +78,8 @@ export interface CompileStageInput {
78
78
  allowSystemFontCapture?: boolean;
79
79
  /** Render-time variable overrides (`--variables`); see CompileForRenderOptions.variables. */
80
80
  variables?: Record<string, unknown>;
81
+ /** Absolute project paths not on disk, readable at these URLs instead. */
82
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
81
83
  }
82
84
  export interface CompileStageResult {
83
85
  compiled: CompiledComposition;
@@ -51,6 +51,7 @@ export interface ExtractVideosStageInput {
51
51
  */
52
52
  materializeSymlinks?: boolean;
53
53
  deferRangeExtraction?: boolean;
54
+ urlByAbsentLocalPath?: ReadonlyMap<string, string>;
54
55
  }
55
56
  export interface ExtractVideosStageResult {
56
57
  /** Result of `extractAllVideoFrames`, or `null` if the composition has no videos. */
@@ -121,6 +121,14 @@ export type PlanFileDigests = Map<string, string>;
121
121
  export declare function planFileSha256(absolutePath: string, digests?: PlanFileDigests): string;
122
122
  /** Hash every file under `dir` into `digests` without blocking the event loop. */
123
123
  export declare function prehashPlanFiles(dir: string, digests: PlanFileDigests): Promise<void>;
124
+ /** Plan file left in remote storage: hashed and published by digest, never copied into the plan. */
125
+ export interface PlanRemoteFile {
126
+ readonly path: string;
127
+ readonly sha256: string;
128
+ readonly sizeBytes: number;
129
+ }
130
+ export declare function writePlanRemoteFiles(planDir: string, files: readonly PlanRemoteFile[]): void;
131
+ export declare function readPlanRemoteFiles(planDir: string): PlanRemoteFile[];
124
132
  /**
125
133
  * Read a frozen plan directory back from disk and recompute its
126
134
  * content-addressed `planHash` over the actual on-disk bytes — including
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.176",
3
+ "version": "0.8.178",
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.176",
39
- "@hyperframes/engine": "0.8.176",
40
- "@hyperframes/parsers": "0.8.176",
41
- "@hyperframes/lint": "0.8.176",
42
- "@hyperframes/studio-server": "0.8.176"
38
+ "@hyperframes/core": "0.8.178",
39
+ "@hyperframes/engine": "0.8.178",
40
+ "@hyperframes/lint": "0.8.178",
41
+ "@hyperframes/parsers": "0.8.178",
42
+ "@hyperframes/studio-server": "0.8.178"
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.176"
51
+ "@hyperframes/aws-lambda": "0.8.178"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"
@@ -1,38 +0,0 @@
1
- /**
2
- * Audio Extractor Service
3
- *
4
- * Extracts audio from media elements in the composition HTML,
5
- * applies timeline positioning, and mixes into a single audio track.
6
- */
7
- export interface AudioElement {
8
- id: string;
9
- src: string;
10
- start: number;
11
- duration: number;
12
- mediaStart: number;
13
- volume: number;
14
- tagName: "audio" | "video";
15
- }
16
- export interface AudioTrack {
17
- id: string;
18
- srcPath: string;
19
- start: number;
20
- duration: number;
21
- mediaStart: number;
22
- volume: number;
23
- }
24
- /**
25
- * Parse audio/video elements from HTML to find media with audio.
26
- */
27
- export declare function parseAudioElements(html: string): AudioElement[];
28
- /**
29
- * Process all audio for a composition.
30
- *
31
- * @param htmlPath - Path to the composition HTML (for parsing media elements)
32
- * @param projectDir - Base directory for resolving relative media paths
33
- * @param workDir - Temporary working directory for intermediate files
34
- * @param outputPath - Final mixed audio output path
35
- * @param totalDuration - Total composition duration in seconds
36
- * @returns true if audio was produced, false if no audio elements found
37
- */
38
- export declare function processAudio(htmlPath: string, projectDir: string, workDir: string, outputPath: string, totalDuration: number): Promise<boolean>;