@hyperframes/producer 0.8.197 → 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/README.md +1 -1
- package/dist/distributed.js +98 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +16 -16
- package/dist/index.js +101 -10
- package/dist/public-server.js +6 -5
- package/dist/services/distributed/chunkWeights.d.ts +16 -0
- package/dist/services/distributed/plan.d.ts +7 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ The producer can also run as a render server, accepting render requests over HTT
|
|
|
35
35
|
import { startServer } from "@hyperframes/producer";
|
|
36
36
|
|
|
37
37
|
await startServer({ port: 8080 });
|
|
38
|
-
// POST /render or /render/stream with projectDir, html, or previewUrl and render options
|
|
38
|
+
// POST /render or /render/stream with projectDir, html, or previewUrl (public HTTPS only) and render options
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Both HTTP routes accept optional `pageMotionBlur: "off"` to disable authored page
|
package/dist/distributed.js
CHANGED
|
@@ -7309,6 +7309,70 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7309
7309
|
}
|
|
7310
7310
|
}
|
|
7311
7311
|
|
|
7312
|
+
// src/services/distributed/chunkWeights.ts
|
|
7313
|
+
import { resolveFrameFormat } from "@hyperframes/engine";
|
|
7314
|
+
var PAGE_BASE_WEIGHT = 0.7;
|
|
7315
|
+
var PNG_WEIGHT_FACTOR = 1.5;
|
|
7316
|
+
function isUniformFrameCost(weights) {
|
|
7317
|
+
let lightest = Infinity;
|
|
7318
|
+
let heaviest = 0;
|
|
7319
|
+
for (const weight of weights) {
|
|
7320
|
+
lightest = Math.min(lightest, weight);
|
|
7321
|
+
heaviest = Math.max(heaviest, weight);
|
|
7322
|
+
}
|
|
7323
|
+
return heaviest <= lightest * 1.05;
|
|
7324
|
+
}
|
|
7325
|
+
function planFrameWeights(input) {
|
|
7326
|
+
const { totalFrames, fps } = input;
|
|
7327
|
+
const delta = new Float64Array(totalFrames + 1);
|
|
7328
|
+
const outputPixels = input.width * input.height;
|
|
7329
|
+
const extractedById = new Map(input.extracted.map((entry) => [entry.videoId, entry]));
|
|
7330
|
+
for (const video of input.videos) {
|
|
7331
|
+
const entry = extractedById.get(video.id);
|
|
7332
|
+
if (!entry || video.hidden) continue;
|
|
7333
|
+
const start = Math.max(0, Math.round(video.start * fps));
|
|
7334
|
+
const end = Math.min(totalFrames, Math.round(video.end * fps));
|
|
7335
|
+
if (!(end > start)) continue;
|
|
7336
|
+
const { width, height } = entry.metadata;
|
|
7337
|
+
const factor = resolveFrameFormat(entry.metadata) === "png" ? PNG_WEIGHT_FACTOR : 1;
|
|
7338
|
+
const weight = Math.min(width * height, outputPixels) / 1e6 * factor;
|
|
7339
|
+
delta[start] = delta[start] + weight;
|
|
7340
|
+
delta[end] = delta[end] - weight;
|
|
7341
|
+
}
|
|
7342
|
+
const weights = new Float64Array(totalFrames);
|
|
7343
|
+
let active = 0;
|
|
7344
|
+
for (let frame = 0; frame < totalFrames; frame++) {
|
|
7345
|
+
active += delta[frame];
|
|
7346
|
+
weights[frame] = PAGE_BASE_WEIGHT + active;
|
|
7347
|
+
}
|
|
7348
|
+
return weights;
|
|
7349
|
+
}
|
|
7350
|
+
function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
7351
|
+
if (isUniformFrameCost(weights)) return null;
|
|
7352
|
+
const totalFrames = weights.length;
|
|
7353
|
+
const total = weights.reduce((sum2, weight) => sum2 + weight, 0);
|
|
7354
|
+
const minSize = Math.max(1, Math.min(minFrames, Math.floor(totalFrames / chunkCount)));
|
|
7355
|
+
const maxSize = Math.max(maxFrames, Math.ceil(totalFrames / chunkCount));
|
|
7356
|
+
const slices = [];
|
|
7357
|
+
let startFrame = 0;
|
|
7358
|
+
let frame = 0;
|
|
7359
|
+
let costBefore = 0;
|
|
7360
|
+
for (let index = 0; index < chunkCount - 1; index++) {
|
|
7361
|
+
const target = (index + 1) * total / chunkCount;
|
|
7362
|
+
while (frame < totalFrames && costBefore + weights[frame] / 2 < target) {
|
|
7363
|
+
costBefore += weights[frame++];
|
|
7364
|
+
}
|
|
7365
|
+
const remaining = chunkCount - index - 1;
|
|
7366
|
+
const low = Math.max(startFrame + minSize, totalFrames - remaining * maxSize);
|
|
7367
|
+
const high = Math.min(startFrame + maxSize, totalFrames - remaining * minSize);
|
|
7368
|
+
const endFrame = Math.min(high, Math.max(low, frame));
|
|
7369
|
+
slices.push({ index, startFrame, endFrame });
|
|
7370
|
+
startFrame = endFrame;
|
|
7371
|
+
}
|
|
7372
|
+
slices.push({ index: chunkCount - 1, startFrame, endFrame: totalFrames });
|
|
7373
|
+
return slices;
|
|
7374
|
+
}
|
|
7375
|
+
|
|
7312
7376
|
// src/services/render/stages/freezePlan.ts
|
|
7313
7377
|
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7314
7378
|
import { join as join17, relative as relative3, resolve as resolve7 } from "node:path";
|
|
@@ -8424,6 +8488,7 @@ var PLAN_PROJECT_DIR_SKIP_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
8424
8488
|
var DEFAULT_CHUNK_SIZE = 240;
|
|
8425
8489
|
var DEFAULT_MAX_PARALLEL_CHUNKS = 16;
|
|
8426
8490
|
var MIN_CHUNK_SIZE = 10;
|
|
8491
|
+
var MAX_CHUNK_SIZE = 3600;
|
|
8427
8492
|
var MAX_PARALLEL_CHUNKS_CEILING = 256;
|
|
8428
8493
|
var ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME = 0.11;
|
|
8429
8494
|
var ESTIMATED_CHUNK_OVERHEAD_SECONDS = 60;
|
|
@@ -8450,6 +8515,11 @@ var ChunkExceedsWorkerTimeoutError = class extends Error {
|
|
|
8450
8515
|
this.name = "ChunkExceedsWorkerTimeoutError";
|
|
8451
8516
|
}
|
|
8452
8517
|
};
|
|
8518
|
+
function framesWithinWorkerTimeout(workerTimeoutSeconds, secondsPerFrame) {
|
|
8519
|
+
return Math.floor(
|
|
8520
|
+
(workerTimeoutSeconds * WORKER_TIMEOUT_MARGIN - ESTIMATED_CHUNK_OVERHEAD_SECONDS) / secondsPerFrame
|
|
8521
|
+
);
|
|
8522
|
+
}
|
|
8453
8523
|
function resolveMaxParallelChunks(config, totalFrames, megapixels, log = defaultLogger, measuredSecondsPerFrame) {
|
|
8454
8524
|
if (config.maxParallelChunks !== void 0) return config.maxParallelChunks;
|
|
8455
8525
|
const { workerTimeoutSeconds, workerConcurrency } = config;
|
|
@@ -8462,9 +8532,7 @@ function resolveMaxParallelChunks(config, totalFrames, megapixels, log = default
|
|
|
8462
8532
|
const minFramesPerChunk = Math.ceil(ESTIMATED_CHUNK_OVERHEAD_SECONDS / secondsPerFrame);
|
|
8463
8533
|
const wide = Math.max(1, Math.min(wave, Math.floor(totalFrames / minFramesPerChunk)));
|
|
8464
8534
|
if (workerTimeoutSeconds === void 0) return wide;
|
|
8465
|
-
const maxFramesPerChunk =
|
|
8466
|
-
(workerTimeoutSeconds * WORKER_TIMEOUT_MARGIN - ESTIMATED_CHUNK_OVERHEAD_SECONDS) / secondsPerFrame
|
|
8467
|
-
);
|
|
8535
|
+
const maxFramesPerChunk = framesWithinWorkerTimeout(workerTimeoutSeconds, secondsPerFrame);
|
|
8468
8536
|
const needed = maxFramesPerChunk < 1 ? Infinity : Math.ceil(totalFrames / maxFramesPerChunk);
|
|
8469
8537
|
if (needed > MAX_PARALLEL_CHUNKS_CEILING) {
|
|
8470
8538
|
const framesAtCeiling = Math.ceil(totalFrames / MAX_PARALLEL_CHUNKS_CEILING);
|
|
@@ -8596,6 +8664,15 @@ function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
|
8596
8664
|
}
|
|
8597
8665
|
return slices;
|
|
8598
8666
|
}
|
|
8667
|
+
function buildPlanChunkSlices(frameWeights, config, chunkCount, effectiveChunkSize, maxFramesPerChunk = Infinity) {
|
|
8668
|
+
const weighted = config.chunkSize === void 0 ? buildWeightedChunkSlices(
|
|
8669
|
+
frameWeights,
|
|
8670
|
+
chunkCount,
|
|
8671
|
+
MIN_CHUNK_SIZE,
|
|
8672
|
+
Math.min(config.targetChunkFrames ?? MAX_CHUNK_SIZE, MAX_CHUNK_SIZE, maxFramesPerChunk)
|
|
8673
|
+
) : null;
|
|
8674
|
+
return weighted ?? buildChunkSlices(frameWeights.length, chunkCount, effectiveChunkSize);
|
|
8675
|
+
}
|
|
8599
8676
|
async function readFontSnapshotSha() {
|
|
8600
8677
|
const module = await import("@hyperframes/core/fonts/embedded-data");
|
|
8601
8678
|
const data = module.EMBEDDED_FONT_DATA;
|
|
@@ -9045,6 +9122,14 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9045
9122
|
const digests = options.digests ?? /* @__PURE__ */ new Map();
|
|
9046
9123
|
const separateAudio = plansSeparateAudio(config, options);
|
|
9047
9124
|
const chunksCarryAudio = !separateAudio && carriesChunkAudio(config, options);
|
|
9125
|
+
const frameWeights = planFrameWeights({
|
|
9126
|
+
videos: planVideosJson.videos,
|
|
9127
|
+
extracted: planVideosJson.extracted,
|
|
9128
|
+
totalFrames,
|
|
9129
|
+
fps: config.fps,
|
|
9130
|
+
width,
|
|
9131
|
+
height
|
|
9132
|
+
});
|
|
9048
9133
|
stageStarted = Date.now();
|
|
9049
9134
|
const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
|
|
9050
9135
|
separateAudio ? null : mixPlanAudio(c, finalCompiledDir).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
@@ -9095,7 +9180,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9095
9180
|
maxParallel,
|
|
9096
9181
|
config.targetChunkFrames
|
|
9097
9182
|
);
|
|
9098
|
-
const chunks =
|
|
9183
|
+
const chunks = buildPlanChunkSlices(
|
|
9184
|
+
frameWeights,
|
|
9185
|
+
config,
|
|
9186
|
+
chunkCount,
|
|
9187
|
+
effectiveChunkSize,
|
|
9188
|
+
chunkSizingUsesFrameCost(config) && config.workerTimeoutSeconds !== void 0 ? framesWithinWorkerTimeout(
|
|
9189
|
+
config.workerTimeoutSeconds,
|
|
9190
|
+
estimateFrameSeconds(megapixels, frameCostOutcome.value)
|
|
9191
|
+
) : void 0
|
|
9192
|
+
);
|
|
9099
9193
|
const audioResult = audioOutcome.value;
|
|
9100
9194
|
if (audioResult?.audioError) {
|
|
9101
9195
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
@@ -11147,7 +11241,6 @@ var ALLOWED_RUNTIME_CAPS = ["lambda", "temporal", "cloud-run-job", "k8s-job", "n
|
|
|
11147
11241
|
var ALLOWED_HDR_MODES = ["auto", "force-sdr"];
|
|
11148
11242
|
var MAX_DIMENSION = 7680;
|
|
11149
11243
|
var MIN_DIMENSION = 16;
|
|
11150
|
-
var MAX_CHUNK_SIZE = 3600;
|
|
11151
11244
|
function validateDistributedRenderConfig(config) {
|
|
11152
11245
|
if (config === null || typeof config !== "object") {
|
|
11153
11246
|
throw new InvalidConfigError("config", "must be an object");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"buildId": "dev",
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "34e66a8b6f8a0c9e7cb0895fc3e22c3a21b0a6240b0b47fb3fcf1621087b919b",
|
|
5
5
|
"artifacts": {
|
|
6
6
|
"iife": "hyperframe.runtime.iife.js",
|
|
7
7
|
"esm": "hyperframe.runtime.mjs"
|