@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/index.js
CHANGED
|
@@ -98670,7 +98670,8 @@ var DEFAULT_CONFIG = {
|
|
|
98670
98670
|
forceScreenshot: false,
|
|
98671
98671
|
enableChunkedEncode: false,
|
|
98672
98672
|
chunkSizeFrames: 360,
|
|
98673
|
-
enableStreamingEncode:
|
|
98673
|
+
enableStreamingEncode: true,
|
|
98674
|
+
streamingEncodeMaxDurationSeconds: 240,
|
|
98674
98675
|
ffmpegEncodeTimeout: 6e5,
|
|
98675
98676
|
ffmpegProcessTimeout: 3e5,
|
|
98676
98677
|
ffmpegStreamingTimeout: 6e5,
|
|
@@ -98732,6 +98733,13 @@ function resolveConfig(overrides) {
|
|
|
98732
98733
|
"PRODUCER_ENABLE_STREAMING_ENCODE",
|
|
98733
98734
|
DEFAULT_CONFIG.enableStreamingEncode
|
|
98734
98735
|
),
|
|
98736
|
+
streamingEncodeMaxDurationSeconds: Math.max(
|
|
98737
|
+
0,
|
|
98738
|
+
envNum(
|
|
98739
|
+
"PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
|
|
98740
|
+
DEFAULT_CONFIG.streamingEncodeMaxDurationSeconds
|
|
98741
|
+
)
|
|
98742
|
+
),
|
|
98735
98743
|
ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegEncodeTimeout),
|
|
98736
98744
|
ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG.ffmpegProcessTimeout),
|
|
98737
98745
|
ffmpegStreamingTimeout: envNum(
|
|
@@ -101674,6 +101682,44 @@ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100)
|
|
|
101674
101682
|
}
|
|
101675
101683
|
return Boolean(await page.evaluate(expression));
|
|
101676
101684
|
}
|
|
101685
|
+
async function applyVideoMetadataHints(page, hints) {
|
|
101686
|
+
if (!hints || hints.length === 0) return;
|
|
101687
|
+
await page.evaluate(
|
|
101688
|
+
(metadataHints) => {
|
|
101689
|
+
for (const hint of metadataHints) {
|
|
101690
|
+
if (!hint.id || !Number.isFinite(hint.width) || !Number.isFinite(hint.height) || hint.width <= 0 || hint.height <= 0) {
|
|
101691
|
+
continue;
|
|
101692
|
+
}
|
|
101693
|
+
const video = document.getElementById(hint.id);
|
|
101694
|
+
if (!video) continue;
|
|
101695
|
+
if (!video.hasAttribute("width")) video.setAttribute("width", String(hint.width));
|
|
101696
|
+
if (!video.hasAttribute("height")) video.setAttribute("height", String(hint.height));
|
|
101697
|
+
const computed = window.getComputedStyle(video);
|
|
101698
|
+
if (!video.style.aspectRatio && (!computed.aspectRatio || computed.aspectRatio === "auto")) {
|
|
101699
|
+
video.style.aspectRatio = `${hint.width} / ${hint.height}`;
|
|
101700
|
+
}
|
|
101701
|
+
}
|
|
101702
|
+
},
|
|
101703
|
+
[...hints]
|
|
101704
|
+
);
|
|
101705
|
+
}
|
|
101706
|
+
async function waitForOptionalTailwindReady(page, timeoutMs) {
|
|
101707
|
+
const hasTailwindReady = await page.evaluate(
|
|
101708
|
+
`(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
|
|
101709
|
+
);
|
|
101710
|
+
if (!hasTailwindReady) return;
|
|
101711
|
+
const ready = await Promise.race([
|
|
101712
|
+
page.evaluate(
|
|
101713
|
+
`Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
|
|
101714
|
+
),
|
|
101715
|
+
new Promise((resolve14) => setTimeout(() => resolve14(false), timeoutMs))
|
|
101716
|
+
]);
|
|
101717
|
+
if (!ready) {
|
|
101718
|
+
throw new Error(
|
|
101719
|
+
`[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
|
|
101720
|
+
);
|
|
101721
|
+
}
|
|
101722
|
+
}
|
|
101677
101723
|
async function initializeSession(session) {
|
|
101678
101724
|
const { page, serverUrl } = session;
|
|
101679
101725
|
page.on("console", (msg) => {
|
|
@@ -101717,6 +101763,7 @@ async function initializeSession(session) {
|
|
|
101717
101763
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
101718
101764
|
);
|
|
101719
101765
|
}
|
|
101766
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
101720
101767
|
const skipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
101721
101768
|
const videosReady = await pollPageExpression(
|
|
101722
101769
|
page,
|
|
@@ -101729,6 +101776,7 @@ async function initializeSession(session) {
|
|
|
101729
101776
|
);
|
|
101730
101777
|
}
|
|
101731
101778
|
await page.evaluate(`document.fonts?.ready`);
|
|
101779
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout2);
|
|
101732
101780
|
if (session.options.format === "png") {
|
|
101733
101781
|
await initTransparentBackground(session.page);
|
|
101734
101782
|
}
|
|
@@ -101783,6 +101831,7 @@ async function initializeSession(session) {
|
|
|
101783
101831
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout}ms. Page must expose window.__hf = { duration, seek }.`
|
|
101784
101832
|
);
|
|
101785
101833
|
}
|
|
101834
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
101786
101835
|
const beginframeSkipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
101787
101836
|
const videoDeadline = Date.now() + (session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout);
|
|
101788
101837
|
while (Date.now() < videoDeadline) {
|
|
@@ -101793,6 +101842,7 @@ async function initializeSession(session) {
|
|
|
101793
101842
|
await new Promise((r) => setTimeout(r, 100));
|
|
101794
101843
|
}
|
|
101795
101844
|
await page.evaluate(`document.fonts?.ready`);
|
|
101845
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout);
|
|
101796
101846
|
warmupRunning = false;
|
|
101797
101847
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
101798
101848
|
if (session.options.format === "png") {
|
|
@@ -110764,6 +110814,24 @@ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
|
110764
110814
|
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
110765
110815
|
});
|
|
110766
110816
|
}
|
|
110817
|
+
function collectVideoReadinessSkipIds(nativeHdrVideoIds, extractedVideos) {
|
|
110818
|
+
return Array.from(
|
|
110819
|
+
/* @__PURE__ */ new Set([
|
|
110820
|
+
...nativeHdrVideoIds,
|
|
110821
|
+
...extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => video.videoId)
|
|
110822
|
+
])
|
|
110823
|
+
).sort();
|
|
110824
|
+
}
|
|
110825
|
+
function hasUsableVideoDimensions(metadata) {
|
|
110826
|
+
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
110827
|
+
}
|
|
110828
|
+
function collectVideoMetadataHints(extractedVideos) {
|
|
110829
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
110830
|
+
id: video.videoId,
|
|
110831
|
+
width: video.metadata.width,
|
|
110832
|
+
height: video.metadata.height
|
|
110833
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
110834
|
+
}
|
|
110767
110835
|
function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled, composition, log = defaultLogger, measuredCaptureCost) {
|
|
110768
110836
|
const captureCost = combineCaptureCostEstimates(
|
|
110769
110837
|
estimateCaptureCostMultiplier(compiled, composition),
|
|
@@ -111429,6 +111497,27 @@ function createRenderJob(config2) {
|
|
|
111429
111497
|
function normalizeCompositionSrcPath(srcPath) {
|
|
111430
111498
|
return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
111431
111499
|
}
|
|
111500
|
+
function createStandaloneEntryRenderClone(root, host) {
|
|
111501
|
+
const hostClone = host.cloneNode(true);
|
|
111502
|
+
hostClone.setAttribute("data-start", "0");
|
|
111503
|
+
if (root === host) return hostClone;
|
|
111504
|
+
const rootClone = root.cloneNode(false);
|
|
111505
|
+
rootClone.appendChild(hostClone);
|
|
111506
|
+
return rootClone;
|
|
111507
|
+
}
|
|
111508
|
+
function replaceBodyWithRenderClone(body, renderClone) {
|
|
111509
|
+
while (body.firstChild) {
|
|
111510
|
+
body.removeChild(body.firstChild);
|
|
111511
|
+
}
|
|
111512
|
+
body.appendChild(renderClone);
|
|
111513
|
+
}
|
|
111514
|
+
function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
|
|
111515
|
+
if (!cfg.enableStreamingEncode) return false;
|
|
111516
|
+
if (outputFormat === "png-sequence") return false;
|
|
111517
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
|
|
111518
|
+
if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
|
|
111519
|
+
return workerCount === 1;
|
|
111520
|
+
}
|
|
111432
111521
|
function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
111433
111522
|
const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
|
|
111434
111523
|
const { document: document2 } = parseHTML(indexHtml);
|
|
@@ -111443,16 +111532,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
111443
111532
|
(candidate) => candidate.hasAttribute("data-composition-id")
|
|
111444
111533
|
) ?? null;
|
|
111445
111534
|
if (!root) return null;
|
|
111446
|
-
const
|
|
111447
|
-
|
|
111448
|
-
body.innerHTML = "";
|
|
111449
|
-
if (root === host) {
|
|
111450
|
-
body.appendChild(hostClone);
|
|
111451
|
-
return document2.toString();
|
|
111452
|
-
}
|
|
111453
|
-
const rootClone = root.cloneNode(false);
|
|
111454
|
-
rootClone.appendChild(hostClone);
|
|
111455
|
-
body.appendChild(rootClone);
|
|
111535
|
+
const renderClone = createStandaloneEntryRenderClone(root, host);
|
|
111536
|
+
replaceBodyWithRenderClone(body, renderClone);
|
|
111456
111537
|
return document2.toString();
|
|
111457
111538
|
}
|
|
111458
111539
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
@@ -111484,7 +111565,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111484
111565
|
}
|
|
111485
111566
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
111486
111567
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
111487
|
-
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
111488
111568
|
let peakRssBytes = 0;
|
|
111489
111569
|
let peakHeapUsedBytes = 0;
|
|
111490
111570
|
const sampleMemory = () => {
|
|
@@ -111778,6 +111858,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111778
111858
|
let frameLookup = null;
|
|
111779
111859
|
const compiledDir = join16(workDir, "compiled");
|
|
111780
111860
|
let extractionResult = null;
|
|
111861
|
+
let videoReadinessSkipIds = [];
|
|
111862
|
+
let videoMetadataHints = [];
|
|
111781
111863
|
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
111782
111864
|
const videoTransfers = /* @__PURE__ */ new Map();
|
|
111783
111865
|
if (job.config.hdrMode !== "force-sdr" && composition.videos.length > 0) {
|
|
@@ -111834,6 +111916,11 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111834
111916
|
if (extractionResult.extracted.length > 0) {
|
|
111835
111917
|
frameLookup = createFrameLookupTable(composition.videos, extractionResult.extracted);
|
|
111836
111918
|
}
|
|
111919
|
+
videoReadinessSkipIds = collectVideoReadinessSkipIds(
|
|
111920
|
+
nativeHdrVideoIds,
|
|
111921
|
+
extractionResult.extracted
|
|
111922
|
+
);
|
|
111923
|
+
videoMetadataHints = collectVideoMetadataHints(extractionResult.extracted);
|
|
111837
111924
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
111838
111925
|
const existingAudioSrcs = new Set(composition.audios.map((a) => a.src));
|
|
111839
111926
|
for (const ext of extractionResult.extracted) {
|
|
@@ -111947,9 +112034,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111947
112034
|
format: needsAlpha ? "png" : "jpeg",
|
|
111948
112035
|
quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
|
|
111949
112036
|
};
|
|
111950
|
-
const
|
|
112037
|
+
const buildCaptureOptions = () => ({
|
|
111951
112038
|
...captureOptions,
|
|
111952
|
-
|
|
112039
|
+
videoMetadataHints,
|
|
112040
|
+
skipReadinessVideoIds: videoReadinessSkipIds
|
|
111953
112041
|
});
|
|
111954
112042
|
let captureCalibration;
|
|
111955
112043
|
let switchedToScreenshotAfterCalibration = false;
|
|
@@ -111962,7 +112050,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
111962
112050
|
calibrationSession = await createCaptureSession(
|
|
111963
112051
|
fileServer.url,
|
|
111964
112052
|
calibrationDir,
|
|
111965
|
-
|
|
112053
|
+
buildCaptureOptions(),
|
|
111966
112054
|
videoInjector,
|
|
111967
112055
|
calibrationCfg
|
|
111968
112056
|
);
|
|
@@ -112045,6 +112133,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112045
112133
|
await closeCaptureSession(probeSession);
|
|
112046
112134
|
probeSession = null;
|
|
112047
112135
|
}
|
|
112136
|
+
let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
|
|
112137
|
+
log.info("streaming-encode gate", {
|
|
112138
|
+
enabled: useStreamingEncode,
|
|
112139
|
+
configFlag: cfg.enableStreamingEncode,
|
|
112140
|
+
outputFormat,
|
|
112141
|
+
workerCount,
|
|
112142
|
+
durationSeconds: job.duration,
|
|
112143
|
+
maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
|
|
112144
|
+
});
|
|
112048
112145
|
const captureAttempts = [];
|
|
112049
112146
|
const FORMAT_EXT = {
|
|
112050
112147
|
mp4: ".mp4",
|
|
@@ -112086,7 +112183,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112086
112183
|
const domSession = await createCaptureSession(
|
|
112087
112184
|
fileServer.url,
|
|
112088
112185
|
framesDir,
|
|
112089
|
-
|
|
112186
|
+
buildCaptureOptions(),
|
|
112090
112187
|
createVideoFrameInjector(frameLookup),
|
|
112091
112188
|
cfg
|
|
112092
112189
|
);
|
|
@@ -112584,28 +112681,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112584
112681
|
} else {
|
|
112585
112682
|
let streamingEncoder = null;
|
|
112586
112683
|
let streamingEncoderClosed = false;
|
|
112587
|
-
if (
|
|
112588
|
-
|
|
112589
|
-
|
|
112590
|
-
|
|
112591
|
-
|
|
112592
|
-
|
|
112593
|
-
|
|
112594
|
-
|
|
112595
|
-
|
|
112596
|
-
|
|
112597
|
-
|
|
112598
|
-
|
|
112599
|
-
|
|
112600
|
-
|
|
112601
|
-
|
|
112602
|
-
|
|
112603
|
-
|
|
112604
|
-
|
|
112605
|
-
|
|
112684
|
+
if (useStreamingEncode) {
|
|
112685
|
+
try {
|
|
112686
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
112687
|
+
videoOnlyPath,
|
|
112688
|
+
{
|
|
112689
|
+
fps: job.config.fps,
|
|
112690
|
+
width,
|
|
112691
|
+
height,
|
|
112692
|
+
codec: preset.codec,
|
|
112693
|
+
preset: preset.preset,
|
|
112694
|
+
quality: effectiveQuality,
|
|
112695
|
+
bitrate: effectiveBitrate,
|
|
112696
|
+
pixelFormat: preset.pixelFormat,
|
|
112697
|
+
useGpu: job.config.useGpu,
|
|
112698
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
112699
|
+
hdr: preset.hdr
|
|
112700
|
+
},
|
|
112701
|
+
abortSignal
|
|
112702
|
+
);
|
|
112703
|
+
assertNotAborted();
|
|
112704
|
+
} catch (err) {
|
|
112705
|
+
if (abortSignal?.aborted) {
|
|
112706
|
+
if (streamingEncoder && !streamingEncoderClosed) {
|
|
112707
|
+
await streamingEncoder.close().catch(() => {
|
|
112708
|
+
});
|
|
112709
|
+
streamingEncoderClosed = true;
|
|
112710
|
+
}
|
|
112711
|
+
throw err;
|
|
112712
|
+
}
|
|
112713
|
+
useStreamingEncode = false;
|
|
112714
|
+
streamingEncoder = null;
|
|
112715
|
+
log.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
|
|
112716
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112717
|
+
outputFormat,
|
|
112718
|
+
workerCount,
|
|
112719
|
+
durationSeconds: job.duration
|
|
112720
|
+
});
|
|
112721
|
+
}
|
|
112606
112722
|
}
|
|
112607
112723
|
try {
|
|
112608
|
-
if (
|
|
112724
|
+
if (useStreamingEncode && streamingEncoder) {
|
|
112609
112725
|
const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
|
|
112610
112726
|
const currentEncoder = streamingEncoder;
|
|
112611
112727
|
if (workerCount > 1) {
|
|
@@ -112619,7 +112735,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112619
112735
|
fileServer.url,
|
|
112620
112736
|
workDir,
|
|
112621
112737
|
tasks,
|
|
112622
|
-
|
|
112738
|
+
buildCaptureOptions(),
|
|
112623
112739
|
() => createVideoFrameInjector(frameLookup),
|
|
112624
112740
|
abortSignal,
|
|
112625
112741
|
(progress) => {
|
|
@@ -112649,7 +112765,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112649
112765
|
const session = probeSession ?? await createCaptureSession(
|
|
112650
112766
|
fileServer.url,
|
|
112651
112767
|
framesDir,
|
|
112652
|
-
|
|
112768
|
+
buildCaptureOptions(),
|
|
112653
112769
|
videoInjector,
|
|
112654
112770
|
cfg
|
|
112655
112771
|
);
|
|
@@ -112704,7 +112820,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112704
112820
|
initialWorkerCount: workerCount,
|
|
112705
112821
|
allowRetry: job.config.workers === void 0,
|
|
112706
112822
|
frameExt: needsAlpha ? "png" : "jpg",
|
|
112707
|
-
captureOptions:
|
|
112823
|
+
captureOptions: buildCaptureOptions(),
|
|
112708
112824
|
createBeforeCaptureHook: () => createVideoFrameInjector(frameLookup),
|
|
112709
112825
|
abortSignal,
|
|
112710
112826
|
onProgress: (progress) => {
|
|
@@ -112739,7 +112855,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
112739
112855
|
const session = probeSession ?? await createCaptureSession(
|
|
112740
112856
|
fileServer.url,
|
|
112741
112857
|
framesDir,
|
|
112742
|
-
|
|
112858
|
+
buildCaptureOptions(),
|
|
112743
112859
|
videoInjector,
|
|
112744
112860
|
cfg
|
|
112745
112861
|
);
|