@hyperframes/producer 0.5.0-alpha.12 → 0.5.0-alpha.14
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/public-server.js
CHANGED
|
@@ -101459,7 +101459,8 @@ var DEFAULT_CONFIG = {
|
|
|
101459
101459
|
forceScreenshot: false,
|
|
101460
101460
|
enableChunkedEncode: false,
|
|
101461
101461
|
chunkSizeFrames: 360,
|
|
101462
|
-
enableStreamingEncode:
|
|
101462
|
+
enableStreamingEncode: true,
|
|
101463
|
+
streamingEncodeMaxDurationSeconds: 240,
|
|
101463
101464
|
ffmpegEncodeTimeout: 6e5,
|
|
101464
101465
|
ffmpegProcessTimeout: 3e5,
|
|
101465
101466
|
ffmpegStreamingTimeout: 6e5,
|
|
@@ -101521,6 +101522,13 @@ function resolveConfig(overrides) {
|
|
|
101521
101522
|
"PRODUCER_ENABLE_STREAMING_ENCODE",
|
|
101522
101523
|
DEFAULT_CONFIG.enableStreamingEncode
|
|
101523
101524
|
),
|
|
101525
|
+
streamingEncodeMaxDurationSeconds: Math.max(
|
|
101526
|
+
0,
|
|
101527
|
+
envNum(
|
|
101528
|
+
"PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
|
|
101529
|
+
DEFAULT_CONFIG.streamingEncodeMaxDurationSeconds
|
|
101530
|
+
)
|
|
101531
|
+
),
|
|
101524
101532
|
ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegEncodeTimeout),
|
|
101525
101533
|
ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegProcessTimeout),
|
|
101526
101534
|
ffmpegStreamingTimeout: envNum(
|
|
@@ -104463,6 +104471,44 @@ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100)
|
|
|
104463
104471
|
}
|
|
104464
104472
|
return Boolean(await page.evaluate(expression));
|
|
104465
104473
|
}
|
|
104474
|
+
async function applyVideoMetadataHints(page, hints) {
|
|
104475
|
+
if (!hints || hints.length === 0) return;
|
|
104476
|
+
await page.evaluate(
|
|
104477
|
+
(metadataHints) => {
|
|
104478
|
+
for (const hint of metadataHints) {
|
|
104479
|
+
if (!hint.id || !Number.isFinite(hint.width) || !Number.isFinite(hint.height) || hint.width <= 0 || hint.height <= 0) {
|
|
104480
|
+
continue;
|
|
104481
|
+
}
|
|
104482
|
+
const video = document.getElementById(hint.id);
|
|
104483
|
+
if (!video) continue;
|
|
104484
|
+
if (!video.hasAttribute("width")) video.setAttribute("width", String(hint.width));
|
|
104485
|
+
if (!video.hasAttribute("height")) video.setAttribute("height", String(hint.height));
|
|
104486
|
+
const computed = window.getComputedStyle(video);
|
|
104487
|
+
if (!video.style.aspectRatio && (!computed.aspectRatio || computed.aspectRatio === "auto")) {
|
|
104488
|
+
video.style.aspectRatio = `${hint.width} / ${hint.height}`;
|
|
104489
|
+
}
|
|
104490
|
+
}
|
|
104491
|
+
},
|
|
104492
|
+
[...hints]
|
|
104493
|
+
);
|
|
104494
|
+
}
|
|
104495
|
+
async function waitForOptionalTailwindReady(page, timeoutMs) {
|
|
104496
|
+
const hasTailwindReady = await page.evaluate(
|
|
104497
|
+
`(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
|
|
104498
|
+
);
|
|
104499
|
+
if (!hasTailwindReady) return;
|
|
104500
|
+
const ready = await Promise.race([
|
|
104501
|
+
page.evaluate(
|
|
104502
|
+
`Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
|
|
104503
|
+
),
|
|
104504
|
+
new Promise((resolve14) => setTimeout(() => resolve14(false), timeoutMs))
|
|
104505
|
+
]);
|
|
104506
|
+
if (!ready) {
|
|
104507
|
+
throw new Error(
|
|
104508
|
+
`[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
|
|
104509
|
+
);
|
|
104510
|
+
}
|
|
104511
|
+
}
|
|
104466
104512
|
async function initializeSession(session) {
|
|
104467
104513
|
const { page, serverUrl } = session;
|
|
104468
104514
|
page.on("console", (msg) => {
|
|
@@ -104506,6 +104552,7 @@ async function initializeSession(session) {
|
|
|
104506
104552
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
104507
104553
|
);
|
|
104508
104554
|
}
|
|
104555
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
104509
104556
|
const skipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
104510
104557
|
const videosReady = await pollPageExpression(
|
|
104511
104558
|
page,
|
|
@@ -104518,6 +104565,7 @@ async function initializeSession(session) {
|
|
|
104518
104565
|
);
|
|
104519
104566
|
}
|
|
104520
104567
|
await page.evaluate(`document.fonts?.ready`);
|
|
104568
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout2);
|
|
104521
104569
|
if (session.options.format === "png") {
|
|
104522
104570
|
await initTransparentBackground(session.page);
|
|
104523
104571
|
}
|
|
@@ -104572,6 +104620,7 @@ async function initializeSession(session) {
|
|
|
104572
104620
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout}ms. Page must expose window.__hf = { duration, seek }.`
|
|
104573
104621
|
);
|
|
104574
104622
|
}
|
|
104623
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
104575
104624
|
const beginframeSkipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
104576
104625
|
const videoDeadline = Date.now() + (session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout);
|
|
104577
104626
|
while (Date.now() < videoDeadline) {
|
|
@@ -104582,6 +104631,7 @@ async function initializeSession(session) {
|
|
|
104582
104631
|
await new Promise((r) => setTimeout(r, 100));
|
|
104583
104632
|
}
|
|
104584
104633
|
await page.evaluate(`document.fonts?.ready`);
|
|
104634
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout);
|
|
104585
104635
|
warmupRunning = false;
|
|
104586
104636
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
104587
104637
|
if (session.options.format === "png") {
|
|
@@ -110929,6 +110979,24 @@ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
|
110929
110979
|
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
110930
110980
|
});
|
|
110931
110981
|
}
|
|
110982
|
+
function collectVideoReadinessSkipIds(nativeHdrVideoIds, extractedVideos) {
|
|
110983
|
+
return Array.from(
|
|
110984
|
+
/* @__PURE__ */ new Set([
|
|
110985
|
+
...nativeHdrVideoIds,
|
|
110986
|
+
...extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => video.videoId)
|
|
110987
|
+
])
|
|
110988
|
+
).sort();
|
|
110989
|
+
}
|
|
110990
|
+
function hasUsableVideoDimensions(metadata) {
|
|
110991
|
+
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
110992
|
+
}
|
|
110993
|
+
function collectVideoMetadataHints(extractedVideos) {
|
|
110994
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
110995
|
+
id: video.videoId,
|
|
110996
|
+
width: video.metadata.width,
|
|
110997
|
+
height: video.metadata.height
|
|
110998
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
110999
|
+
}
|
|
110932
111000
|
function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled, composition, log = defaultLogger, measuredCaptureCost) {
|
|
110933
111001
|
const captureCost = combineCaptureCostEstimates(
|
|
110934
111002
|
estimateCaptureCostMultiplier(compiled, composition),
|
|
@@ -111594,6 +111662,27 @@ function createRenderJob(config2) {
|
|
|
111594
111662
|
function normalizeCompositionSrcPath(srcPath) {
|
|
111595
111663
|
return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
111596
111664
|
}
|
|
111665
|
+
function createStandaloneEntryRenderClone(root, host) {
|
|
111666
|
+
const hostClone = host.cloneNode(true);
|
|
111667
|
+
hostClone.setAttribute("data-start", "0");
|
|
111668
|
+
if (root === host) return hostClone;
|
|
111669
|
+
const rootClone = root.cloneNode(false);
|
|
111670
|
+
rootClone.appendChild(hostClone);
|
|
111671
|
+
return rootClone;
|
|
111672
|
+
}
|
|
111673
|
+
function replaceBodyWithRenderClone(body, renderClone) {
|
|
111674
|
+
while (body.firstChild) {
|
|
111675
|
+
body.removeChild(body.firstChild);
|
|
111676
|
+
}
|
|
111677
|
+
body.appendChild(renderClone);
|
|
111678
|
+
}
|
|
111679
|
+
function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
|
|
111680
|
+
if (!cfg.enableStreamingEncode) return false;
|
|
111681
|
+
if (outputFormat === "png-sequence") return false;
|
|
111682
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
|
|
111683
|
+
if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
|
|
111684
|
+
return workerCount === 1;
|
|
111685
|
+
}
|
|
111597
111686
|
function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
111598
111687
|
const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
|
|
111599
111688
|
const { document: document2 } = parseHTML(indexHtml);
|
|
@@ -111608,16 +111697,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
111608
111697
|
(candidate) => candidate.hasAttribute("data-composition-id")
|
|
111609
111698
|
) ?? null;
|
|
111610
111699
|
if (!root) return null;
|
|
111611
|
-
const
|
|
111612
|
-
|
|
111613
|
-
body.innerHTML = "";
|
|
111614
|
-
if (root === host) {
|
|
111615
|
-
body.appendChild(hostClone);
|
|
111616
|
-
return document2.toString();
|
|
111617
|
-
}
|
|
111618
|
-
const rootClone = root.cloneNode(false);
|
|
111619
|
-
rootClone.appendChild(hostClone);
|
|
111620
|
-
body.appendChild(rootClone);
|
|
111700
|
+
const renderClone = createStandaloneEntryRenderClone(root, host);
|
|
111701
|
+
replaceBodyWithRenderClone(body, renderClone);
|
|
111621
111702
|
return document2.toString();
|
|
111622
111703
|
}
|
|
111623
111704
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
@@ -111649,7 +111730,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111649
111730
|
}
|
|
111650
111731
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
111651
111732
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
111652
|
-
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
111653
111733
|
let peakRssBytes = 0;
|
|
111654
111734
|
let peakHeapUsedBytes = 0;
|
|
111655
111735
|
const sampleMemory = () => {
|
|
@@ -111943,6 +112023,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111943
112023
|
let frameLookup = null;
|
|
111944
112024
|
const compiledDir = join16(workDir, "compiled");
|
|
111945
112025
|
let extractionResult = null;
|
|
112026
|
+
let videoReadinessSkipIds = [];
|
|
112027
|
+
let videoMetadataHints = [];
|
|
111946
112028
|
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
111947
112029
|
const videoTransfers = /* @__PURE__ */ new Map();
|
|
111948
112030
|
if (job.config.hdrMode !== "force-sdr" && composition.videos.length > 0) {
|
|
@@ -111999,6 +112081,11 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111999
112081
|
if (extractionResult.extracted.length > 0) {
|
|
112000
112082
|
frameLookup = createFrameLookupTable(composition.videos, extractionResult.extracted);
|
|
112001
112083
|
}
|
|
112084
|
+
videoReadinessSkipIds = collectVideoReadinessSkipIds(
|
|
112085
|
+
nativeHdrVideoIds,
|
|
112086
|
+
extractionResult.extracted
|
|
112087
|
+
);
|
|
112088
|
+
videoMetadataHints = collectVideoMetadataHints(extractionResult.extracted);
|
|
112002
112089
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
112003
112090
|
const existingAudioSrcs = new Set(composition.audios.map((a) => a.src));
|
|
112004
112091
|
for (const ext of extractionResult.extracted) {
|
|
@@ -112112,9 +112199,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112112
112199
|
format: needsAlpha ? "png" : "jpeg",
|
|
112113
112200
|
quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
|
|
112114
112201
|
};
|
|
112115
|
-
const
|
|
112202
|
+
const buildCaptureOptions = () => ({
|
|
112116
112203
|
...captureOptions,
|
|
112117
|
-
|
|
112204
|
+
videoMetadataHints,
|
|
112205
|
+
skipReadinessVideoIds: videoReadinessSkipIds
|
|
112118
112206
|
});
|
|
112119
112207
|
let captureCalibration;
|
|
112120
112208
|
let switchedToScreenshotAfterCalibration = false;
|
|
@@ -112127,7 +112215,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112127
112215
|
calibrationSession = await createCaptureSession(
|
|
112128
112216
|
fileServer.url,
|
|
112129
112217
|
calibrationDir,
|
|
112130
|
-
|
|
112218
|
+
buildCaptureOptions(),
|
|
112131
112219
|
videoInjector,
|
|
112132
112220
|
calibrationCfg
|
|
112133
112221
|
);
|
|
@@ -112210,6 +112298,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112210
112298
|
await closeCaptureSession(probeSession);
|
|
112211
112299
|
probeSession = null;
|
|
112212
112300
|
}
|
|
112301
|
+
let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
|
|
112302
|
+
log.info("streaming-encode gate", {
|
|
112303
|
+
enabled: useStreamingEncode,
|
|
112304
|
+
configFlag: cfg.enableStreamingEncode,
|
|
112305
|
+
outputFormat,
|
|
112306
|
+
workerCount,
|
|
112307
|
+
durationSeconds: job.duration,
|
|
112308
|
+
maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
|
|
112309
|
+
});
|
|
112213
112310
|
const captureAttempts = [];
|
|
112214
112311
|
const FORMAT_EXT = {
|
|
112215
112312
|
mp4: ".mp4",
|
|
@@ -112251,7 +112348,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112251
112348
|
const domSession = await createCaptureSession(
|
|
112252
112349
|
fileServer.url,
|
|
112253
112350
|
framesDir,
|
|
112254
|
-
|
|
112351
|
+
buildCaptureOptions(),
|
|
112255
112352
|
createVideoFrameInjector(frameLookup),
|
|
112256
112353
|
cfg
|
|
112257
112354
|
);
|
|
@@ -112749,28 +112846,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112749
112846
|
} else {
|
|
112750
112847
|
let streamingEncoder = null;
|
|
112751
112848
|
let streamingEncoderClosed = false;
|
|
112752
|
-
if (
|
|
112753
|
-
|
|
112754
|
-
|
|
112755
|
-
|
|
112756
|
-
|
|
112757
|
-
|
|
112758
|
-
|
|
112759
|
-
|
|
112760
|
-
|
|
112761
|
-
|
|
112762
|
-
|
|
112763
|
-
|
|
112764
|
-
|
|
112765
|
-
|
|
112766
|
-
|
|
112767
|
-
|
|
112768
|
-
|
|
112769
|
-
|
|
112770
|
-
|
|
112849
|
+
if (useStreamingEncode) {
|
|
112850
|
+
try {
|
|
112851
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
112852
|
+
videoOnlyPath,
|
|
112853
|
+
{
|
|
112854
|
+
fps: job.config.fps,
|
|
112855
|
+
width,
|
|
112856
|
+
height,
|
|
112857
|
+
codec: preset.codec,
|
|
112858
|
+
preset: preset.preset,
|
|
112859
|
+
quality: effectiveQuality,
|
|
112860
|
+
bitrate: effectiveBitrate,
|
|
112861
|
+
pixelFormat: preset.pixelFormat,
|
|
112862
|
+
useGpu: job.config.useGpu,
|
|
112863
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
112864
|
+
hdr: preset.hdr
|
|
112865
|
+
},
|
|
112866
|
+
abortSignal
|
|
112867
|
+
);
|
|
112868
|
+
assertNotAborted();
|
|
112869
|
+
} catch (err) {
|
|
112870
|
+
if (abortSignal?.aborted) {
|
|
112871
|
+
if (streamingEncoder && !streamingEncoderClosed) {
|
|
112872
|
+
await streamingEncoder.close().catch(() => {
|
|
112873
|
+
});
|
|
112874
|
+
streamingEncoderClosed = true;
|
|
112875
|
+
}
|
|
112876
|
+
throw err;
|
|
112877
|
+
}
|
|
112878
|
+
useStreamingEncode = false;
|
|
112879
|
+
streamingEncoder = null;
|
|
112880
|
+
log.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
|
|
112881
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112882
|
+
outputFormat,
|
|
112883
|
+
workerCount,
|
|
112884
|
+
durationSeconds: job.duration
|
|
112885
|
+
});
|
|
112886
|
+
}
|
|
112771
112887
|
}
|
|
112772
112888
|
try {
|
|
112773
|
-
if (
|
|
112889
|
+
if (useStreamingEncode && streamingEncoder) {
|
|
112774
112890
|
const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
|
|
112775
112891
|
const currentEncoder = streamingEncoder;
|
|
112776
112892
|
if (workerCount > 1) {
|
|
@@ -112784,7 +112900,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112784
112900
|
fileServer.url,
|
|
112785
112901
|
workDir,
|
|
112786
112902
|
tasks,
|
|
112787
|
-
|
|
112903
|
+
buildCaptureOptions(),
|
|
112788
112904
|
() => createVideoFrameInjector(frameLookup),
|
|
112789
112905
|
abortSignal,
|
|
112790
112906
|
(progress) => {
|
|
@@ -112814,7 +112930,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112814
112930
|
const session = probeSession ?? await createCaptureSession(
|
|
112815
112931
|
fileServer.url,
|
|
112816
112932
|
framesDir,
|
|
112817
|
-
|
|
112933
|
+
buildCaptureOptions(),
|
|
112818
112934
|
videoInjector,
|
|
112819
112935
|
cfg
|
|
112820
112936
|
);
|
|
@@ -112869,7 +112985,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112869
112985
|
initialWorkerCount: workerCount,
|
|
112870
112986
|
allowRetry: job.config.workers === void 0,
|
|
112871
112987
|
frameExt: needsAlpha ? "png" : "jpg",
|
|
112872
|
-
captureOptions:
|
|
112988
|
+
captureOptions: buildCaptureOptions(),
|
|
112873
112989
|
createBeforeCaptureHook: () => createVideoFrameInjector(frameLookup),
|
|
112874
112990
|
abortSignal,
|
|
112875
112991
|
onProgress: (progress) => {
|
|
@@ -112904,7 +113020,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112904
113020
|
const session = probeSession ?? await createCaptureSession(
|
|
112905
113021
|
fileServer.url,
|
|
112906
113022
|
framesDir,
|
|
112907
|
-
|
|
113023
|
+
buildCaptureOptions(),
|
|
112908
113024
|
videoInjector,
|
|
112909
113025
|
cfg
|
|
112910
113026
|
);
|