@hyperframes/producer 0.8.231 → 0.9.1
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 +6 -5
- package/dist/distributed.js +56 -96
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +70 -70
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -22
- package/dist/regression-harness-distributed.d.ts +1 -1
- package/dist/regression-harness-lambda-local-types.d.ts +1 -5
- package/package.json +7 -8
- package/dist/plan-parity-analysis.d.ts +0 -4
- package/dist/plan-parity-contract.d.ts +0 -139
- package/dist/plan-parity-fixture.d.ts +0 -18
- package/dist/plan-parity-harness.d.ts +0 -82
package/dist/distributed.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* `@hyperframes/producer/distributed` — the distributed render primitives.
|
|
3
3
|
*
|
|
4
4
|
* The distributed activities are pure functions over local file paths;
|
|
5
|
-
* networking + orchestration live in adapters.
|
|
6
|
-
*
|
|
5
|
+
* networking + orchestration live in adapters. Plan v2 is the only public
|
|
6
|
+
* transport; chunk and assemble primitives consume its materialized execution
|
|
7
|
+
* directory.
|
|
7
8
|
*
|
|
8
9
|
* Adopters (AWS Lambda, Cloud Run Jobs, Temporal, K8s Jobs, plain SSH):
|
|
9
10
|
*
|
|
@@ -29,8 +30,8 @@
|
|
|
29
30
|
* No networking, no AWS SDK, no Temporal SDK — those live in adapter
|
|
30
31
|
* packages. This module is library code only.
|
|
31
32
|
*/
|
|
32
|
-
export { buildChunkSlices, measurePlanDirBytes,
|
|
33
|
-
export { createPlanV2FromExecutionPlan,
|
|
33
|
+
export { buildChunkSlices, measurePlanDirBytes, rejectUnsupportedDistributedFormat, resolveChunkPlan, buildPlanAudio, plansSeparateAudio, type DistributedRenderConfig, type RemoteProjectFile, DEFAULT_CHUNK_SIZE, DEFAULT_MAX_PARALLEL_CHUNKS, MIN_CHUNK_SIZE, PLAN_DIR_SIZE_LIMIT_BYTES, PLAN_PROJECT_DIR_SKIP_SEGMENTS, CHUNK_EXCEEDS_WORKER_TIMEOUT, ChunkExceedsWorkerTimeoutError, FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED, FormatNotSupportedInDistributedError, } from "./services/distributed/plan.js";
|
|
34
|
+
export { createPlanV2FromExecutionPlan, getPlanV2ExecutionPlanHash, isPlanV2DeferredSourceArtifact, listPlanV2ArtifactsForTarget, materializePlanV2Target, planV2, planV2WithPublisher, publishPlanV2FromExecutionPlan, readPlanV2Manifest, validatePlanV2MaterializedTarget, PLAN_V2_INTEGRITY_UNRECOVERABLE, PLAN_V2_MATERIALIZATION_MARKER, PlanV2IntegrityError, type PlanV2Artifact, type PlanV2Limitations, type PlanV2Manifest, type PlanV2MaterializeOptions, type PlanV2MaterializationResult, type PlanV2MaterializationTarget, type PlanV2RemoteSources, type PlanV2Result, type PlanV2WithPublisherOptions, } from "./services/distributed/planV2.js";
|
|
34
35
|
export { LocalPlanV2ArtifactPublisher, type LocalPlanV2ArtifactPublisherOptions, type PlanV2ArtifactPublisher, type PlanV2PublishBlob, } from "./services/distributed/planV2Publisher.js";
|
|
35
36
|
export { assembleV2, renderChunkV2 } from "./services/distributed/planV2Execution.js";
|
|
36
37
|
export { applyRuntimeEnvSnapshot, readWebGlVendorInfoFromCanvas, renderChunk, type ChunkFrameRange, type ChunkRenderer, type ChunkResult, type EffectiveChunkResult, type RenderChunkOptions, CHUNK_FRAME_RANGE_INVALID, FFMPEG_VERSION_MISMATCH, INVALID_VIDEO_METADATA, PLAN_HASH_MISMATCH, RenderChunkValidationError, } from "./services/distributed/renderChunk.js";
|
|
@@ -41,7 +42,7 @@ export { InvalidConfigError, type SerializableDistributedRenderConfig, validateD
|
|
|
41
42
|
export { hashProjectDir, listSiteFiles } from "./services/distributed/projectHash.js";
|
|
42
43
|
export { AUDIO_SIDECAR_MIN_BYTES, buildAudioSidecar, wantsAudioSidecar, type AudioSidecarOutcome, } from "./services/distributed/audioSidecar.js";
|
|
43
44
|
export { buildProbeFacts, wantsProbeFacts, type ProbeFactsOutcome, } from "./services/distributed/probeFacts.js";
|
|
44
|
-
export {
|
|
45
|
+
export { PLAN_PROTOCOL_V2, PLAN_PROTOCOL_UNSUPPORTED, PLAN_V2_ARTIFACT_LAYOUT, PLAN_V2_HASH_SCHEMA, PLAN_V2_SCHEMA_VERSION, PlanProtocolUnsupportedError, type PlanProtocolDescriptor, type PlanProtocolV2Descriptor, } from "./services/distributed/planProtocol.js";
|
|
45
46
|
export { PlanVideosMetadataError, type DistributedFormat } from "./services/distributed/shared.js";
|
|
46
47
|
export { isPlanAudioArtifactPath, PLAN_AUDIO_LEGACY_RELATIVE_PATH, PLAN_AUDIO_RELATIVE_PATH, resolvePlanAudioPath, } from "./services/distributed/shared.js";
|
|
47
48
|
export type { ChunkSliceJson, CompositionMetadataJson, LockedRenderConfig, } from "./services/render/stages/freezePlan.js";
|
package/dist/distributed.js
CHANGED
|
@@ -6085,14 +6085,14 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6085
6085
|
);
|
|
6086
6086
|
}
|
|
6087
6087
|
const targetDurationSeconds = videoInfo.frameCount * videoInfo.fpsDen / videoInfo.fpsNum;
|
|
6088
|
-
const
|
|
6088
|
+
const plan = buildPadTrimAudioPlan(
|
|
6089
6089
|
input.audioPath,
|
|
6090
6090
|
input.outputPath,
|
|
6091
6091
|
audioInfo.durationSeconds,
|
|
6092
6092
|
targetDurationSeconds
|
|
6093
6093
|
);
|
|
6094
6094
|
try {
|
|
6095
|
-
for (const step of
|
|
6095
|
+
for (const step of plan.steps) {
|
|
6096
6096
|
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
|
|
6097
6097
|
if (!ffmpegResult.success) {
|
|
6098
6098
|
return {
|
|
@@ -6100,7 +6100,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6100
6100
|
outputPath: input.outputPath,
|
|
6101
6101
|
targetDurationSeconds,
|
|
6102
6102
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
6103
|
-
operation:
|
|
6103
|
+
operation: plan.operation,
|
|
6104
6104
|
error: ffmpegResult.error,
|
|
6105
6105
|
failureReason: ffmpegResult.failureReason
|
|
6106
6106
|
};
|
|
@@ -6112,11 +6112,11 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6112
6112
|
outputPath: input.outputPath,
|
|
6113
6113
|
targetDurationSeconds,
|
|
6114
6114
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
6115
|
-
operation:
|
|
6116
|
-
error: `audioPadTrim: failed to materialize ${
|
|
6115
|
+
operation: plan.operation,
|
|
6116
|
+
error: `audioPadTrim: failed to materialize ${plan.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
6117
6117
|
};
|
|
6118
6118
|
} finally {
|
|
6119
|
-
for (const path of
|
|
6119
|
+
for (const path of plan.cleanupPaths) removeScratch(path);
|
|
6120
6120
|
}
|
|
6121
6121
|
let loweredDb = 0;
|
|
6122
6122
|
if (probeTruePeak) {
|
|
@@ -6133,7 +6133,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6133
6133
|
outputPath: input.outputPath,
|
|
6134
6134
|
targetDurationSeconds,
|
|
6135
6135
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
6136
|
-
operation:
|
|
6136
|
+
operation: plan.operation,
|
|
6137
6137
|
error: correction.error,
|
|
6138
6138
|
failureReason: correction.failureReason
|
|
6139
6139
|
};
|
|
@@ -6145,7 +6145,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6145
6145
|
outputPath: input.outputPath,
|
|
6146
6146
|
targetDurationSeconds,
|
|
6147
6147
|
sourceDurationSeconds: audioInfo.durationSeconds,
|
|
6148
|
-
operation:
|
|
6148
|
+
operation: plan.operation,
|
|
6149
6149
|
...loweredDb > 0 ? { audioLoweredDb: loweredDb } : {}
|
|
6150
6150
|
};
|
|
6151
6151
|
}
|
|
@@ -6634,7 +6634,7 @@ async function runCaptureStage(input) {
|
|
|
6634
6634
|
job,
|
|
6635
6635
|
totalFrames,
|
|
6636
6636
|
cfg,
|
|
6637
|
-
plan
|
|
6637
|
+
plan,
|
|
6638
6638
|
log,
|
|
6639
6639
|
captureAttempts,
|
|
6640
6640
|
buildCaptureOptions,
|
|
@@ -6645,8 +6645,8 @@ async function runCaptureStage(input) {
|
|
|
6645
6645
|
dedupPerfs
|
|
6646
6646
|
} = input;
|
|
6647
6647
|
let { probeSession } = input;
|
|
6648
|
-
let { workerCount } =
|
|
6649
|
-
const { forceScreenshot } =
|
|
6648
|
+
let { workerCount } = plan;
|
|
6649
|
+
const { forceScreenshot } = plan;
|
|
6650
6650
|
let lastBrowserConsole = [];
|
|
6651
6651
|
const workerPageErrors = [];
|
|
6652
6652
|
let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
|
|
@@ -8358,7 +8358,6 @@ var PLAN_PROTOCOL_V1 = Object.freeze({
|
|
|
8358
8358
|
artifactLayout: PLAN_ARTIFACT_LAYOUT,
|
|
8359
8359
|
hashSchema: PLAN_HASH_SCHEMA
|
|
8360
8360
|
});
|
|
8361
|
-
var CURRENT_PLAN_PROTOCOL = PLAN_PROTOCOL_V1;
|
|
8362
8361
|
var PLAN_PROTOCOL_V2 = Object.freeze({
|
|
8363
8362
|
schemaVersion: PLAN_V2_SCHEMA_VERSION,
|
|
8364
8363
|
artifactLayout: PLAN_V2_ARTIFACT_LAYOUT,
|
|
@@ -8380,9 +8379,6 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
|
|
|
8380
8379
|
}),
|
|
8381
8380
|
chunkFrameRange: true
|
|
8382
8381
|
});
|
|
8383
|
-
function getDistributedRenderCapabilities() {
|
|
8384
|
-
return DISTRIBUTED_RENDER_CAPABILITIES;
|
|
8385
|
-
}
|
|
8386
8382
|
var PlanProtocolUnsupportedError = class extends Error {
|
|
8387
8383
|
// Public adapters inspect this typed code even though OSS producer does not.
|
|
8388
8384
|
// fallow-ignore-next-line unused-class-member
|
|
@@ -8956,10 +8952,10 @@ function buildStbl(stsd, track) {
|
|
|
8956
8952
|
);
|
|
8957
8953
|
}
|
|
8958
8954
|
var codecOf = (track) => track.stsd.toString("latin1", 20, 24);
|
|
8959
|
-
var sameTrack = (track,
|
|
8960
|
-
function planTracksOf(tracks,
|
|
8955
|
+
var sameTrack = (track, plan) => track.handler === plan.handler && track.timescale === plan.timescale && codecOf(track) === codecOf(plan);
|
|
8956
|
+
function planTracksOf(tracks, plan, index) {
|
|
8961
8957
|
let next = 0;
|
|
8962
|
-
const matched =
|
|
8958
|
+
const matched = plan.map((planTrack) => {
|
|
8963
8959
|
const track = tracks[next];
|
|
8964
8960
|
if (track && sameTrack(track, planTrack)) return tracks[next++];
|
|
8965
8961
|
if (planTrack.handler === "soun") return null;
|
|
@@ -8980,22 +8976,22 @@ function sourceTracks(layouts, audio) {
|
|
|
8980
8976
|
if (audio && (audioTracks.length !== 1 || audioTracks[0].handler !== "soun")) {
|
|
8981
8977
|
throw new Error("[mp4Stitch] the separate audio file must hold one audio track");
|
|
8982
8978
|
}
|
|
8983
|
-
const
|
|
8979
|
+
const plan = [...chunkPlan, ...audioTracks];
|
|
8984
8980
|
const chunks = layouts.map((layout, i) => {
|
|
8985
8981
|
const tracks = readTracks(layout.moov);
|
|
8986
8982
|
if (audio && tracks.some((track) => track.handler === "soun")) {
|
|
8987
8983
|
throw new Error(`[mp4Stitch] chunk ${i} carries its own audio`);
|
|
8988
8984
|
}
|
|
8989
|
-
return planTracksOf(tracks,
|
|
8985
|
+
return planTracksOf(tracks, plan, i);
|
|
8990
8986
|
});
|
|
8991
8987
|
return {
|
|
8992
|
-
plan
|
|
8988
|
+
plan,
|
|
8993
8989
|
chunkTrackCount: chunkPlan.length,
|
|
8994
8990
|
sources: audio ? [[...chunkPlan.map(() => null), audioTracks[0]], ...chunks] : chunks
|
|
8995
8991
|
};
|
|
8996
8992
|
}
|
|
8997
8993
|
function buildStitchedHeader(layouts, audio) {
|
|
8998
|
-
const { plan
|
|
8994
|
+
const { plan, chunkTrackCount, sources } = sourceTracks(layouts, audio);
|
|
8999
8995
|
const sourceLayouts = audio ? [audio, ...layouts] : layouts;
|
|
9000
8996
|
const payloadBases = [];
|
|
9001
8997
|
let payloadBytes = 0;
|
|
@@ -9007,10 +9003,10 @@ function buildStitchedHeader(layouts, audio) {
|
|
|
9007
9003
|
const root = boxAt(template, 0, 0, template.length);
|
|
9008
9004
|
const mvhd = child2(template, root, "mvhd");
|
|
9009
9005
|
const movieTimescale = timescaleOf(template, mvhd);
|
|
9010
|
-
const chunkTrackIds =
|
|
9006
|
+
const chunkTrackIds = plan.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
|
|
9011
9007
|
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
9012
9008
|
const buildMoov = (dataStart2) => {
|
|
9013
|
-
const traks =
|
|
9009
|
+
const traks = plan.map((track, t) => {
|
|
9014
9010
|
const parts = sources.flatMap((tracks, chunk) => {
|
|
9015
9011
|
const part = tracks[t];
|
|
9016
9012
|
return part ? [{ ...part, chunk }] : [];
|
|
@@ -10297,22 +10293,6 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10297
10293
|
rmSync7(c.workDir, { recursive: true, force: true });
|
|
10298
10294
|
}
|
|
10299
10295
|
}
|
|
10300
|
-
async function plan(projectDir, config, planDir, options = {}) {
|
|
10301
|
-
const executionPlan = await buildLocalExecutionPlan(projectDir, config, planDir, options);
|
|
10302
|
-
return {
|
|
10303
|
-
planDir: executionPlan.executionPlanDir,
|
|
10304
|
-
planProtocol: PLAN_PROTOCOL_V1,
|
|
10305
|
-
planHash: executionPlan.executionPlanHash,
|
|
10306
|
-
chunkCount: executionPlan.chunkCount,
|
|
10307
|
-
totalFrames: executionPlan.totalFrames,
|
|
10308
|
-
fps: executionPlan.fps,
|
|
10309
|
-
width: executionPlan.width,
|
|
10310
|
-
height: executionPlan.height,
|
|
10311
|
-
format: executionPlan.format,
|
|
10312
|
-
ffmpegVersion: executionPlan.ffmpegVersion,
|
|
10313
|
-
producerVersion: executionPlan.producerVersion
|
|
10314
|
-
};
|
|
10315
|
-
}
|
|
10316
10296
|
|
|
10317
10297
|
// src/services/distributed/planV2.ts
|
|
10318
10298
|
import {
|
|
@@ -10857,9 +10837,6 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
10857
10837
|
throw error;
|
|
10858
10838
|
}
|
|
10859
10839
|
}
|
|
10860
|
-
function createPlanV2FromV1(executionPlanDir, planV2Dir) {
|
|
10861
|
-
return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
|
|
10862
|
-
}
|
|
10863
10840
|
async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
|
|
10864
10841
|
return publishExecutionPlan(executionPlanDir, publisher, {});
|
|
10865
10842
|
}
|
|
@@ -10889,9 +10866,6 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
|
|
|
10889
10866
|
throw error;
|
|
10890
10867
|
}
|
|
10891
10868
|
}
|
|
10892
|
-
async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
10893
|
-
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
10894
|
-
}
|
|
10895
10869
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
10896
10870
|
const stagingRoot = mkdtempSync4(join26(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
10897
10871
|
const digests = /* @__PURE__ */ new Map();
|
|
@@ -10951,8 +10925,8 @@ function resultFromManifest(planV2Dir, manifest) {
|
|
|
10951
10925
|
limitations: manifest.limitations
|
|
10952
10926
|
};
|
|
10953
10927
|
}
|
|
10954
|
-
function getPlanV2ExecutionPlanHash(
|
|
10955
|
-
return
|
|
10928
|
+
function getPlanV2ExecutionPlanHash(plan) {
|
|
10929
|
+
return plan.sourcePlanV1Hash;
|
|
10956
10930
|
}
|
|
10957
10931
|
function readString2(value, field) {
|
|
10958
10932
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -11242,10 +11216,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11242
11216
|
const start = Date.now();
|
|
11243
11217
|
const log = options?.logger ?? defaultLogger;
|
|
11244
11218
|
const cfr = options?.cfr === true;
|
|
11245
|
-
const
|
|
11219
|
+
const plan = readAssemblePlan(planDir, chunkOutputs);
|
|
11246
11220
|
const chunkPaths = chunkOutputs.flat();
|
|
11247
|
-
if (
|
|
11248
|
-
return mergePngFrameDirs(chunkPaths, outputPath,
|
|
11221
|
+
if (plan.dimensions.format === "png-sequence") {
|
|
11222
|
+
return mergePngFrameDirs(chunkPaths, outputPath, plan.totalFrames, audioPath, start);
|
|
11249
11223
|
}
|
|
11250
11224
|
if (chunksAreStitchedMp4(planDir, cfr)) {
|
|
11251
11225
|
const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
|
|
@@ -11253,17 +11227,17 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11253
11227
|
return {
|
|
11254
11228
|
outputPath,
|
|
11255
11229
|
durationMs: Date.now() - start,
|
|
11256
|
-
framesEncoded:
|
|
11230
|
+
framesEncoded: plan.totalFrames,
|
|
11257
11231
|
fileSize: statSync8(outputPath).size
|
|
11258
11232
|
};
|
|
11259
11233
|
}
|
|
11260
|
-
const format =
|
|
11234
|
+
const format = plan.dimensions.format;
|
|
11261
11235
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11262
11236
|
if (!existsSync19(dirname14(outputPath))) {
|
|
11263
11237
|
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11264
11238
|
}
|
|
11265
11239
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
11266
|
-
const fps = { num:
|
|
11240
|
+
const fps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
11267
11241
|
const stageMs = {};
|
|
11268
11242
|
const ctx = {
|
|
11269
11243
|
workDir,
|
|
@@ -11305,7 +11279,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11305
11279
|
return {
|
|
11306
11280
|
outputPath,
|
|
11307
11281
|
durationMs: Date.now() - start,
|
|
11308
|
-
framesEncoded:
|
|
11282
|
+
framesEncoded: plan.totalFrames,
|
|
11309
11283
|
fileSize
|
|
11310
11284
|
};
|
|
11311
11285
|
}
|
|
@@ -11315,8 +11289,8 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11315
11289
|
if (!existsSync19(planJsonPath)) {
|
|
11316
11290
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11317
11291
|
}
|
|
11318
|
-
const
|
|
11319
|
-
readPlanProtocolV1(
|
|
11292
|
+
const plan = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
|
|
11293
|
+
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11320
11294
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11321
11295
|
if (!existsSync19(chunksJsonPath)) {
|
|
11322
11296
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
@@ -11337,7 +11311,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11337
11311
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11338
11312
|
}
|
|
11339
11313
|
}
|
|
11340
|
-
return
|
|
11314
|
+
return plan;
|
|
11341
11315
|
}
|
|
11342
11316
|
function videoInputArgs(ctx, videoSources) {
|
|
11343
11317
|
return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
|
|
@@ -11467,8 +11441,8 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11467
11441
|
return lockedAudio(planDir).separateAudio === true;
|
|
11468
11442
|
}
|
|
11469
11443
|
function planFormat(planDir) {
|
|
11470
|
-
const
|
|
11471
|
-
return
|
|
11444
|
+
const plan = JSON.parse(readFileSync9(join27(planDir, "plan.json"), "utf-8"));
|
|
11445
|
+
return plan.dimensions.format;
|
|
11472
11446
|
}
|
|
11473
11447
|
function lockedAudio(planDir) {
|
|
11474
11448
|
const encoderJsonPath = join27(planDir, "meta", "encoder.json");
|
|
@@ -11691,8 +11665,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11691
11665
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
11692
11666
|
);
|
|
11693
11667
|
}
|
|
11694
|
-
const
|
|
11695
|
-
readPlanProtocolV1(
|
|
11668
|
+
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11669
|
+
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
11696
11670
|
const planHashStarted = Date.now();
|
|
11697
11671
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
11698
11672
|
role: "chunk",
|
|
@@ -11750,10 +11724,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11750
11724
|
);
|
|
11751
11725
|
}
|
|
11752
11726
|
const ffmpegVersion = await readFfmpegVersion();
|
|
11753
|
-
if (ffmpegVersion !==
|
|
11727
|
+
if (ffmpegVersion !== plan.ffmpegVersion) {
|
|
11754
11728
|
throw new RenderChunkValidationError(
|
|
11755
11729
|
FFMPEG_VERSION_MISMATCH,
|
|
11756
|
-
`[renderChunk] ffmpeg version on this worker does not match planDir. planDir: ${JSON.stringify(
|
|
11730
|
+
`[renderChunk] ffmpeg version on this worker does not match planDir. planDir: ${JSON.stringify(plan.ffmpegVersion)}; worker: ${JSON.stringify(ffmpegVersion)}. Distributed retries require byte-identical ffmpeg builds across workers. Re-plan from a worker matching this version, or run all renders on an image with the planDir's ffmpeg.`
|
|
11757
11731
|
);
|
|
11758
11732
|
}
|
|
11759
11733
|
if (encoder.browserGpuMode !== "software") {
|
|
@@ -11762,12 +11736,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11762
11736
|
`[renderChunk] planDir requires browserGpuMode=software, got ${JSON.stringify(encoder.browserGpuMode)}.`
|
|
11763
11737
|
);
|
|
11764
11738
|
}
|
|
11765
|
-
const recomputedPlanHash = v2Manifest === null ? recomputePlanHashFromPlanDir(planDir) :
|
|
11739
|
+
const recomputedPlanHash = v2Manifest === null ? recomputePlanHashFromPlanDir(planDir) : plan.planHash;
|
|
11766
11740
|
const planHashMs = Date.now() - planHashStarted;
|
|
11767
|
-
if (recomputedPlanHash !==
|
|
11741
|
+
if (recomputedPlanHash !== plan.planHash) {
|
|
11768
11742
|
throw new RenderChunkValidationError(
|
|
11769
11743
|
PLAN_HASH_MISMATCH,
|
|
11770
|
-
`[renderChunk] planDir content fingerprint does not match plan.json.planHash. plan.json: ${
|
|
11744
|
+
`[renderChunk] planDir content fingerprint does not match plan.json.planHash. plan.json: ${plan.planHash}; recomputed: ${recomputedPlanHash}. Likely a corrupted artifact (partial S3 download, manual tampering) or a planDir produced by an incompatible producer version. Re-plan and re-fan-out.`
|
|
11771
11745
|
);
|
|
11772
11746
|
}
|
|
11773
11747
|
if (!encoder.runtimeEnv || typeof encoder.runtimeEnv !== "object") {
|
|
@@ -11779,16 +11753,16 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11779
11753
|
const envRestore = applyRuntimeEnvSnapshot(encoder.runtimeEnv);
|
|
11780
11754
|
try {
|
|
11781
11755
|
const job = buildSyntheticRenderJob({
|
|
11782
|
-
fps: { num:
|
|
11756
|
+
fps: { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen },
|
|
11783
11757
|
quality: encoder.quality,
|
|
11784
|
-
format:
|
|
11758
|
+
format: plan.dimensions.format,
|
|
11785
11759
|
crf: encoder.crf,
|
|
11786
11760
|
bitrate: encoder.bitrate,
|
|
11787
11761
|
hdrMode: "force-sdr",
|
|
11788
11762
|
entryFile: "index.html"
|
|
11789
11763
|
});
|
|
11790
11764
|
job.totalFrames = framesInChunk;
|
|
11791
|
-
job.duration = framesInChunk *
|
|
11765
|
+
job.duration = framesInChunk * plan.dimensions.fpsDen / plan.dimensions.fpsNum;
|
|
11792
11766
|
const cfg = {
|
|
11793
11767
|
...resolveConfig5(),
|
|
11794
11768
|
browserGpuMode: "software",
|
|
@@ -11804,7 +11778,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11804
11778
|
mkdirSync14(framesDir, { recursive: true });
|
|
11805
11779
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
11806
11780
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
11807
|
-
const chunkFps = { num:
|
|
11781
|
+
const chunkFps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
11808
11782
|
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
11809
11783
|
const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
11810
11784
|
const remoteVideoIds = new Set(
|
|
@@ -11819,10 +11793,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11819
11793
|
preHeadScripts: [buildVirtualTimeShim({ seedRandomFromFrame: true })],
|
|
11820
11794
|
// These dimensions are frozen by the controller from the render job, so
|
|
11821
11795
|
// chunk runtime seek quantization stays on the same fps grid as capture.
|
|
11822
|
-
fps: { num:
|
|
11796
|
+
fps: { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen }
|
|
11823
11797
|
});
|
|
11824
11798
|
const captureOptions = buildChunkCaptureOptions({
|
|
11825
|
-
dimensions:
|
|
11799
|
+
dimensions: plan.dimensions,
|
|
11826
11800
|
encoder,
|
|
11827
11801
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11828
11802
|
html: readFileSync10(join28(compiledDir, "index.html"), "utf-8"),
|
|
@@ -11979,9 +11953,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11979
11953
|
videoFrameLookup,
|
|
11980
11954
|
frameCacheOptions(cfg, chunkWorkerCount)
|
|
11981
11955
|
);
|
|
11982
|
-
const isPngSequence =
|
|
11956
|
+
const isPngSequence = plan.dimensions.format === "png-sequence";
|
|
11983
11957
|
outputKind = isPngSequence ? "frame-dir" : "file";
|
|
11984
|
-
const presetFormat = isPngSequence ? "mp4" :
|
|
11958
|
+
const presetFormat = isPngSequence ? "mp4" : plan.dimensions.format;
|
|
11985
11959
|
const basePreset = getEncoderPreset6(
|
|
11986
11960
|
job.config.quality,
|
|
11987
11961
|
presetFormat,
|
|
@@ -12004,9 +11978,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12004
11978
|
outputPath: videoOnlyPath,
|
|
12005
11979
|
framesDir,
|
|
12006
11980
|
videoOnlyPath,
|
|
12007
|
-
width: Math.round(
|
|
12008
|
-
height: Math.round(
|
|
12009
|
-
needsAlpha:
|
|
11981
|
+
width: Math.round(plan.dimensions.width * encoder.deviceScaleFactor),
|
|
11982
|
+
height: Math.round(plan.dimensions.height * encoder.deviceScaleFactor),
|
|
11983
|
+
needsAlpha: plan.dimensions.format !== "mp4",
|
|
12010
11984
|
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
12011
11985
|
// The encoder writes video only; audio joins at assemble time or in the
|
|
12012
11986
|
// chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
|
|
@@ -12065,7 +12039,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12065
12039
|
useLayeredComposite: false,
|
|
12066
12040
|
usePageSideCompositing: false,
|
|
12067
12041
|
hasHdrContent: false,
|
|
12068
|
-
needsAlpha:
|
|
12042
|
+
needsAlpha: plan.dimensions.format !== "mp4"
|
|
12069
12043
|
});
|
|
12070
12044
|
if (capturePlan.kind !== "sdr_disk") {
|
|
12071
12045
|
throw new Error(`Distributed chunk requires sdr_disk plan; got ${capturePlan.kind}`);
|
|
@@ -12155,7 +12129,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12155
12129
|
outputPath: outputChunkPath,
|
|
12156
12130
|
chunks: audioChunks.chunks,
|
|
12157
12131
|
chunkIndex: audioChunks.index,
|
|
12158
|
-
fps: { num:
|
|
12132
|
+
fps: { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen },
|
|
12159
12133
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
12160
12134
|
});
|
|
12161
12135
|
rmSync11(videoOnlyPath, { force: true });
|
|
@@ -12185,7 +12159,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12185
12159
|
const durationMs = Date.now() - start;
|
|
12186
12160
|
const perfPath = `${outputChunkPath}.perf.json`;
|
|
12187
12161
|
const perfPayload = {
|
|
12188
|
-
planHash:
|
|
12162
|
+
planHash: plan.planHash,
|
|
12189
12163
|
chunkIndex,
|
|
12190
12164
|
startFrame: range.startFrame,
|
|
12191
12165
|
endFrame: range.endFrame,
|
|
@@ -12203,7 +12177,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12203
12177
|
videoShownBoxes: Object.fromEntries(shownBoxes ?? []),
|
|
12204
12178
|
sha256,
|
|
12205
12179
|
outputKind,
|
|
12206
|
-
producerVersion:
|
|
12180
|
+
producerVersion: plan.producerVersion,
|
|
12207
12181
|
ffmpegVersion
|
|
12208
12182
|
};
|
|
12209
12183
|
writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
@@ -12643,12 +12617,10 @@ export {
|
|
|
12643
12617
|
AUDIO_SIDECAR_MIN_BYTES,
|
|
12644
12618
|
CHUNK_EXCEEDS_WORKER_TIMEOUT,
|
|
12645
12619
|
CHUNK_FRAME_RANGE_INVALID,
|
|
12646
|
-
CURRENT_PLAN_PROTOCOL,
|
|
12647
12620
|
ChunkExceedsWorkerTimeoutError,
|
|
12648
12621
|
DEFAULT_CHUNK_SIZE,
|
|
12649
12622
|
DEFAULT_MAX_PARALLEL_CHUNKS,
|
|
12650
12623
|
DISTRIBUTED_DURATION_OUT_OF_RANGE,
|
|
12651
|
-
DISTRIBUTED_RENDER_CAPABILITIES,
|
|
12652
12624
|
FFMPEG_VERSION_MISMATCH,
|
|
12653
12625
|
FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED,
|
|
12654
12626
|
FormatNotSupportedInDistributedError,
|
|
@@ -12657,25 +12629,19 @@ export {
|
|
|
12657
12629
|
LocalPlanV2ArtifactPublisher,
|
|
12658
12630
|
MAX_DISTRIBUTED_DURATION_SECONDS,
|
|
12659
12631
|
MIN_CHUNK_SIZE,
|
|
12660
|
-
PLAN_ARTIFACT_LAYOUT,
|
|
12661
12632
|
PLAN_AUDIO_LEGACY_RELATIVE_PATH,
|
|
12662
12633
|
PLAN_AUDIO_RELATIVE_PATH,
|
|
12663
12634
|
PLAN_DIR_SIZE_LIMIT_BYTES,
|
|
12664
12635
|
PLAN_HASH_MISMATCH,
|
|
12665
|
-
PLAN_HASH_SCHEMA,
|
|
12666
12636
|
PLAN_PROJECT_DIR_SKIP_SEGMENTS,
|
|
12667
12637
|
PLAN_PROTOCOL_UNSUPPORTED,
|
|
12668
|
-
PLAN_PROTOCOL_V1,
|
|
12669
12638
|
PLAN_PROTOCOL_V2,
|
|
12670
|
-
PLAN_SCHEMA_VERSION,
|
|
12671
|
-
PLAN_TOO_LARGE,
|
|
12672
12639
|
PLAN_V2_ARTIFACT_LAYOUT,
|
|
12673
12640
|
PLAN_V2_HASH_SCHEMA,
|
|
12674
12641
|
PLAN_V2_INTEGRITY_UNRECOVERABLE,
|
|
12675
12642
|
PLAN_V2_MATERIALIZATION_MARKER,
|
|
12676
12643
|
PLAN_V2_SCHEMA_VERSION,
|
|
12677
12644
|
PlanProtocolUnsupportedError,
|
|
12678
|
-
PlanTooLargeError,
|
|
12679
12645
|
PlanV2IntegrityError,
|
|
12680
12646
|
PlanValidationError,
|
|
12681
12647
|
PlanVideosMetadataError,
|
|
@@ -12690,8 +12656,6 @@ export {
|
|
|
12690
12656
|
buildStitchedHeader,
|
|
12691
12657
|
chunksAreStitchedMp4,
|
|
12692
12658
|
createPlanV2FromExecutionPlan,
|
|
12693
|
-
createPlanV2FromV1,
|
|
12694
|
-
getDistributedRenderCapabilities,
|
|
12695
12659
|
getPlanV2ExecutionPlanHash,
|
|
12696
12660
|
hashProjectDir,
|
|
12697
12661
|
isPlanAudioArtifactPath,
|
|
@@ -12701,15 +12665,11 @@ export {
|
|
|
12701
12665
|
materializePlanV2Target,
|
|
12702
12666
|
measurePlanDirBytes,
|
|
12703
12667
|
muxChunkWithAudio,
|
|
12704
|
-
plan,
|
|
12705
12668
|
planV2,
|
|
12706
12669
|
planV2WithPublisher,
|
|
12707
12670
|
plansSeparateAudio,
|
|
12708
12671
|
publishPlanV2FromExecutionPlan,
|
|
12709
|
-
publishPlanV2FromV1,
|
|
12710
12672
|
readMp4Layout,
|
|
12711
|
-
readPlanProtocol,
|
|
12712
|
-
readPlanProtocolV1,
|
|
12713
12673
|
readPlanV2Manifest,
|
|
12714
12674
|
readWebGlVendorInfoFromCanvas2 as readWebGlVendorInfoFromCanvas,
|
|
12715
12675
|
rejectUnsupportedDistributedFormat,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"buildId": "dev",
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "6018b9f4174f36358ea66bf885e50e05ed18042628340e4e488bbaf5584e8a8a",
|
|
5
5
|
"artifacts": {
|
|
6
6
|
"iife": "hyperframe.runtime.iife.js",
|
|
7
7
|
"esm": "hyperframe.runtime.mjs"
|