@hyperframes/producer 0.8.177 → 0.8.179
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/distributed.d.ts +3 -3
- package/dist/distributed.js +1472 -1239
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +57 -55
- package/dist/index.js +1364 -1154
- package/dist/public-server.js +40 -13
- package/dist/services/distributed/chunkAudio.d.ts +11 -0
- package/dist/services/distributed/chunkFrames.d.ts +4 -0
- package/dist/services/distributed/mp4Stitch.d.ts +1 -0
- package/dist/services/distributed/plan.d.ts +10 -4
- package/dist/services/distributed/planV2.d.ts +15 -2
- package/dist/services/distributed/planV2Publisher.d.ts +1 -0
- package/dist/services/distributed/projectHash.d.ts +13 -8
- package/dist/services/htmlCompiler.d.ts +2 -0
- package/dist/services/render/stages/audioStage.d.ts +1 -0
- package/dist/services/render/stages/compileStage.d.ts +2 -0
- package/dist/services/render/stages/extractVideosStage.d.ts +1 -0
- package/dist/services/render/stages/freezePlan.d.ts +8 -0
- package/package.json +7 -7
- package/dist/services/audioExtractor.d.ts +0 -38
package/dist/public-server.js
CHANGED
|
@@ -1407,9 +1407,9 @@ import {
|
|
|
1407
1407
|
} from "node:fs";
|
|
1408
1408
|
import { hostname, uptime } from "node:os";
|
|
1409
1409
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1410
|
+
import { RENDER_TEMP_DIR } from "@hyperframes/core/project-folders";
|
|
1410
1411
|
var OWNER_FILE = "owner.json";
|
|
1411
1412
|
var TRANSACTION_BACKUP = "backup";
|
|
1412
|
-
var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
|
|
1413
1413
|
function pidScope() {
|
|
1414
1414
|
const attempt = (read) => {
|
|
1415
1415
|
try {
|
|
@@ -6086,7 +6086,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
6086
6086
|
}
|
|
6087
6087
|
return false;
|
|
6088
6088
|
}
|
|
6089
|
-
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
|
|
6089
|
+
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
6090
6090
|
let filePath = src;
|
|
6091
6091
|
if (isHttpUrl(src)) {
|
|
6092
6092
|
if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
|
|
@@ -6100,8 +6100,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6100
6100
|
} else {
|
|
6101
6101
|
filePath = decodedUrlPath(src);
|
|
6102
6102
|
if (!filePath.startsWith("/")) filePath = join13(baseDir, filePath);
|
|
6103
|
+
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
6103
6104
|
}
|
|
6104
|
-
if (!existsSync10(filePath)) {
|
|
6105
|
+
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
6105
6106
|
return { duration: null, resolvedPath: filePath };
|
|
6106
6107
|
}
|
|
6107
6108
|
const withSrcContext = (error) => {
|
|
@@ -6164,7 +6165,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
|
6164
6165
|
}
|
|
6165
6166
|
return changed ? document2.toString() : html;
|
|
6166
6167
|
}
|
|
6167
|
-
async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
6168
|
+
async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
|
|
6168
6169
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
|
|
6169
6170
|
const mediaUnresolved = unresolved.filter(
|
|
6170
6171
|
(el) => (el.tagName === "video" || el.tagName === "audio") && el.src
|
|
@@ -6180,7 +6181,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
|
6180
6181
|
downloadDir,
|
|
6181
6182
|
el.tagName,
|
|
6182
6183
|
el.id,
|
|
6183
|
-
log
|
|
6184
|
+
log,
|
|
6185
|
+
urlByAbsentLocalPath
|
|
6184
6186
|
).then(({ duration }) => ({ id: el.id, duration }))
|
|
6185
6187
|
)
|
|
6186
6188
|
);
|
|
@@ -6222,7 +6224,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
|
6222
6224
|
compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
|
|
6223
6225
|
return { html: compiledHtml, unresolvedCompositions };
|
|
6224
6226
|
}
|
|
6225
|
-
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
|
|
6227
|
+
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
6226
6228
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
6227
6229
|
const { document: document2 } = parseHTML4(html);
|
|
6228
6230
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
@@ -6248,13 +6250,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6248
6250
|
const { html: compiledSub } = await compileHtmlFile(
|
|
6249
6251
|
item.rawSubHtml,
|
|
6250
6252
|
dirname6(item.filePath),
|
|
6251
|
-
downloadDir
|
|
6253
|
+
downloadDir,
|
|
6254
|
+
void 0,
|
|
6255
|
+
urlByAbsentLocalPath
|
|
6252
6256
|
);
|
|
6253
6257
|
const nested = await parseSubCompositions(
|
|
6254
6258
|
compiledSub,
|
|
6255
6259
|
projectDir,
|
|
6256
6260
|
downloadDir,
|
|
6257
|
-
item.nestedVisited
|
|
6261
|
+
item.nestedVisited,
|
|
6262
|
+
urlByAbsentLocalPath
|
|
6258
6263
|
);
|
|
6259
6264
|
return {
|
|
6260
6265
|
srcPath: item.srcPath,
|
|
@@ -7012,9 +7017,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7012
7017
|
rawHtml,
|
|
7013
7018
|
projectDir,
|
|
7014
7019
|
downloadDir,
|
|
7015
|
-
options.log
|
|
7020
|
+
options.log,
|
|
7021
|
+
options.urlByAbsentLocalPath
|
|
7022
|
+
);
|
|
7023
|
+
const { subCompositions } = await parseSubCompositions(
|
|
7024
|
+
compiledHtml,
|
|
7025
|
+
projectDir,
|
|
7026
|
+
downloadDir,
|
|
7027
|
+
void 0,
|
|
7028
|
+
options.urlByAbsentLocalPath
|
|
7016
7029
|
);
|
|
7017
|
-
const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
|
|
7018
7030
|
const fullHtml = ensureFullDocument(compiledHtml);
|
|
7019
7031
|
const inlinedHtml = inlineSubCompositions(
|
|
7020
7032
|
fullHtml,
|
|
@@ -7510,6 +7522,7 @@ async function runCompileStage(input) {
|
|
|
7510
7522
|
allowSystemFontCapture,
|
|
7511
7523
|
abortSignal,
|
|
7512
7524
|
variables: input.variables,
|
|
7525
|
+
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7513
7526
|
animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7514
7527
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7515
7528
|
});
|
|
@@ -8472,7 +8485,8 @@ async function runExtractVideosStage(input) {
|
|
|
8472
8485
|
abortSignal,
|
|
8473
8486
|
assertNotAborted,
|
|
8474
8487
|
materializeSymlinks,
|
|
8475
|
-
deferRangeExtraction
|
|
8488
|
+
deferRangeExtraction,
|
|
8489
|
+
urlByAbsentLocalPath
|
|
8476
8490
|
} = input;
|
|
8477
8491
|
const stage2Start = Date.now();
|
|
8478
8492
|
const extractionPolicy = resolveVideoExtractionPolicy();
|
|
@@ -8566,7 +8580,8 @@ async function runExtractVideosStage(input) {
|
|
|
8566
8580
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
8567
8581
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
8568
8582
|
deferRangeExtraction,
|
|
8569
|
-
contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
|
|
8583
|
+
contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
8584
|
+
urlByAbsentLocalPath
|
|
8570
8585
|
},
|
|
8571
8586
|
abortSignal,
|
|
8572
8587
|
cfg,
|
|
@@ -8649,6 +8664,7 @@ async function runAudioStage(input) {
|
|
|
8649
8664
|
audioGain,
|
|
8650
8665
|
audios,
|
|
8651
8666
|
masterFxChain,
|
|
8667
|
+
urlByAbsentLocalPath,
|
|
8652
8668
|
abortSignal,
|
|
8653
8669
|
assertNotAborted,
|
|
8654
8670
|
log
|
|
@@ -8668,7 +8684,7 @@ async function runAudioStage(input) {
|
|
|
8668
8684
|
audioOutputPath,
|
|
8669
8685
|
duration,
|
|
8670
8686
|
abortSignal,
|
|
8671
|
-
{ ffmpegProcessTimeout, audioGain, masterFxChain },
|
|
8687
|
+
{ ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
|
|
8672
8688
|
compiledDir
|
|
8673
8689
|
);
|
|
8674
8690
|
} catch (err) {
|
|
@@ -13835,6 +13851,17 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
|
|
|
13835
13851
|
// src/services/render/stages/planHash.ts
|
|
13836
13852
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
13837
13853
|
|
|
13854
|
+
// src/services/distributed/chunkAudio.ts
|
|
13855
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
|
|
13856
|
+
|
|
13857
|
+
// src/services/distributed/mp4Stitch.ts
|
|
13858
|
+
var SCAN_BYTES = 64 * 1024;
|
|
13859
|
+
|
|
13860
|
+
// src/services/distributed/chunkAudio.ts
|
|
13861
|
+
var SLICE_DIR = "audio-chunks";
|
|
13862
|
+
var SLICE_FRAME = `${SLICE_DIR}/frame.m4a`;
|
|
13863
|
+
var SLICE_LAYOUT = `${SLICE_DIR}/layout.json`;
|
|
13864
|
+
|
|
13838
13865
|
// src/services/distributed/planSize.ts
|
|
13839
13866
|
var PLAN_ROOT_CATEGORY = {
|
|
13840
13867
|
compiled: "compiled",
|
|
@@ -15,6 +15,17 @@ interface AudioCut {
|
|
|
15
15
|
packetCount: number;
|
|
16
16
|
}
|
|
17
17
|
export declare function audioCutForChunk(grid: AacPacketGrid, chunks: readonly ChunkSliceJson[], index: number, fps: Fps): AudioCut | null;
|
|
18
|
+
export declare function chunkAudioSlicePath(index: number): string;
|
|
19
|
+
/** Which chunks need a sliced-audio file: `"all"`, one index, or `null` if it is not one. */
|
|
20
|
+
export declare function chunkAudioSliceTarget(path: string): "all" | number | null;
|
|
21
|
+
export declare function writeChunkAudioSlices(input: {
|
|
22
|
+
audioPath: string;
|
|
23
|
+
planDir: string;
|
|
24
|
+
chunks: readonly ChunkSliceJson[];
|
|
25
|
+
fps: Fps;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
/** The plan audio for a chunk: the whole file, or one rebuilt at `scratchPath` from its slice. */
|
|
28
|
+
export declare function chunkAudioSource(planDir: string, chunkIndex: number, scratchPath: string): string | null;
|
|
18
29
|
interface MuxInput {
|
|
19
30
|
videoPath: string;
|
|
20
31
|
audioPath: string | null;
|
|
@@ -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[]>;
|
|
@@ -32,6 +32,7 @@ export interface TrackTable {
|
|
|
32
32
|
/** Each track's sample tables, expanded to one entry per sample (and per chunk). */
|
|
33
33
|
export declare function readTracks(moov: Buffer): TrackTable[];
|
|
34
34
|
export declare function buildStitchedHeader(layouts: readonly Mp4Layout[]): Buffer;
|
|
35
|
+
export declare function fileRange(path: string): ReadRange;
|
|
35
36
|
/** Local join: the stitched header, then each chunk's mdat payload in order. Like the ffmpeg
|
|
36
37
|
* join, it writes in a work folder beside `outputPath` and renames only a finished file there. */
|
|
37
38
|
export declare function writeStitchedMp4(chunkPaths: readonly string[], outputPath: string): Promise<void>;
|
|
@@ -201,15 +201,21 @@ 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;
|
|
212
|
+
/** With `chunksCarryAudio`, also write each chunk's audio packets to their own file. Plan v2 only. */
|
|
213
|
+
readonly sliceChunkAudio?: boolean;
|
|
214
|
+
}
|
|
215
|
+
export interface RemoteProjectFile {
|
|
216
|
+
readonly url: string;
|
|
217
|
+
readonly sha256: string;
|
|
218
|
+
readonly sizeBytes: number;
|
|
213
219
|
}
|
|
214
220
|
export declare function logPlanStage(log: ProducerLogger, stage: string, startedAt: number): void;
|
|
215
221
|
/**
|
|
@@ -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
|
|
6
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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.
|
|
18
|
-
*
|
|
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.
|
|
3
|
+
"version": "0.8.179",
|
|
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/
|
|
39
|
-
"@hyperframes/
|
|
40
|
-
"@hyperframes/
|
|
41
|
-
"@hyperframes/studio-server": "0.8.
|
|
42
|
-
"@hyperframes/
|
|
38
|
+
"@hyperframes/core": "0.8.179",
|
|
39
|
+
"@hyperframes/engine": "0.8.179",
|
|
40
|
+
"@hyperframes/lint": "0.8.179",
|
|
41
|
+
"@hyperframes/studio-server": "0.8.179",
|
|
42
|
+
"@hyperframes/parsers": "0.8.179"
|
|
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.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.8.179"
|
|
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>;
|