@hyperframes/producer 0.4.5 → 0.4.7
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/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/index.js +1317 -293
- package/dist/index.js.map +4 -4
- package/dist/public-server.js +1317 -293
- package/dist/public-server.js.map +4 -4
- package/dist/services/fileServer.d.ts +26 -0
- package/dist/services/fileServer.d.ts.map +1 -1
- package/dist/services/htmlCompiler.d.ts +11 -0
- package/dist/services/htmlCompiler.d.ts.map +1 -1
- package/dist/services/renderOrchestrator.d.ts +3 -11
- package/dist/services/renderOrchestrator.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -89421,6 +89421,7 @@ import {
|
|
|
89421
89421
|
mkdirSync as mkdirSync10,
|
|
89422
89422
|
rmSync as rmSync3,
|
|
89423
89423
|
readFileSync as readFileSync9,
|
|
89424
|
+
readdirSync as readdirSync6,
|
|
89424
89425
|
writeFileSync as writeFileSync4,
|
|
89425
89426
|
copyFileSync as copyFileSync2,
|
|
89426
89427
|
appendFileSync
|
|
@@ -98668,6 +98669,8 @@ var DEFAULT_CONFIG = {
|
|
|
98668
98669
|
ffmpegEncodeTimeout: 6e5,
|
|
98669
98670
|
ffmpegProcessTimeout: 3e5,
|
|
98670
98671
|
ffmpegStreamingTimeout: 6e5,
|
|
98672
|
+
hdr: false,
|
|
98673
|
+
hdrAutoDetect: true,
|
|
98671
98674
|
audioGain: 1.35,
|
|
98672
98675
|
frameDataUriCacheLimit: 256,
|
|
98673
98676
|
playerReadyTimeout: 45e3,
|
|
@@ -98724,6 +98727,12 @@ function resolveConfig(overrides) {
|
|
|
98724
98727
|
"FFMPEG_STREAMING_TIMEOUT_MS",
|
|
98725
98728
|
DEFAULT_CONFIG.ffmpegStreamingTimeout
|
|
98726
98729
|
),
|
|
98730
|
+
hdr: (() => {
|
|
98731
|
+
const raw2 = env2("PRODUCER_HDR_TRANSFER");
|
|
98732
|
+
if (raw2 === "hlg" || raw2 === "pq") return { transfer: raw2 };
|
|
98733
|
+
return void 0;
|
|
98734
|
+
})(),
|
|
98735
|
+
hdrAutoDetect: envBool("PRODUCER_HDR_AUTO_DETECT", DEFAULT_CONFIG.hdrAutoDetect),
|
|
98727
98736
|
audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG.audioGain),
|
|
98728
98737
|
frameDataUriCacheLimit: Math.max(
|
|
98729
98738
|
32,
|
|
@@ -98920,7 +98929,8 @@ function buildChromeArgs(options, config2) {
|
|
|
98920
98929
|
"--font-render-hinting=none",
|
|
98921
98930
|
"--force-color-profile=srgb",
|
|
98922
98931
|
`--window-size=${options.width},${options.height}`,
|
|
98923
|
-
//
|
|
98932
|
+
// Prevent Chrome from throttling background tabs/timers — critical when the
|
|
98933
|
+
// page is offscreen during headless capture
|
|
98924
98934
|
"--disable-background-timer-throttling",
|
|
98925
98935
|
"--disable-backgrounding-occluded-windows",
|
|
98926
98936
|
"--disable-renderer-backgrounding",
|
|
@@ -100907,6 +100917,24 @@ async function pageScreenshotCapture(page, options) {
|
|
|
100907
100917
|
});
|
|
100908
100918
|
return Buffer.from(result.data, "base64");
|
|
100909
100919
|
}
|
|
100920
|
+
async function initTransparentBackground(page) {
|
|
100921
|
+
const client = await getCdpSession(page);
|
|
100922
|
+
await client.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
100923
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
100924
|
+
});
|
|
100925
|
+
}
|
|
100926
|
+
async function captureAlphaPng(page, width, height) {
|
|
100927
|
+
const client = await getCdpSession(page);
|
|
100928
|
+
const result = await client.send("Page.captureScreenshot", {
|
|
100929
|
+
format: "png",
|
|
100930
|
+
fromSurface: true,
|
|
100931
|
+
captureBeyondViewport: false,
|
|
100932
|
+
optimizeForSpeed: false,
|
|
100933
|
+
// must be false to preserve alpha
|
|
100934
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
100935
|
+
});
|
|
100936
|
+
return Buffer.from(result.data, "base64");
|
|
100937
|
+
}
|
|
100910
100938
|
async function injectVideoFramesBatch(page, updates) {
|
|
100911
100939
|
if (updates.length === 0) return;
|
|
100912
100940
|
await page.evaluate(
|
|
@@ -100928,16 +100956,7 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
100928
100956
|
video.parentNode?.insertBefore(img, video.nextSibling);
|
|
100929
100957
|
}
|
|
100930
100958
|
if (!img) continue;
|
|
100931
|
-
|
|
100932
|
-
img.style.position = computedStyle.position;
|
|
100933
|
-
img.style.width = computedStyle.width;
|
|
100934
|
-
img.style.height = computedStyle.height;
|
|
100935
|
-
img.style.top = computedStyle.top;
|
|
100936
|
-
img.style.left = computedStyle.left;
|
|
100937
|
-
img.style.right = computedStyle.right;
|
|
100938
|
-
img.style.bottom = computedStyle.bottom;
|
|
100939
|
-
img.style.inset = computedStyle.inset;
|
|
100940
|
-
} else {
|
|
100959
|
+
{
|
|
100941
100960
|
const videoRect = video.getBoundingClientRect();
|
|
100942
100961
|
const offsetLeft = Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0;
|
|
100943
100962
|
const offsetTop = Number.isFinite(video.offsetTop) ? video.offsetTop : 0;
|
|
@@ -100988,14 +101007,22 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
|
|
|
100988
101007
|
const active = new Set(ids);
|
|
100989
101008
|
const videos = Array.from(document.querySelectorAll("video[data-start]"));
|
|
100990
101009
|
for (const video of videos) {
|
|
100991
|
-
if (active.has(video.id)) continue;
|
|
100992
|
-
video.style.removeProperty("display");
|
|
100993
|
-
video.style.setProperty("visibility", "hidden", "important");
|
|
100994
|
-
video.style.setProperty("opacity", "0", "important");
|
|
100995
|
-
video.style.setProperty("pointer-events", "none", "important");
|
|
100996
101010
|
const img = video.nextElementSibling;
|
|
100997
|
-
|
|
100998
|
-
|
|
101011
|
+
const hasImg = img && img.classList.contains("__render_frame__");
|
|
101012
|
+
if (active.has(video.id)) {
|
|
101013
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
101014
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
101015
|
+
if (hasImg) {
|
|
101016
|
+
img.style.visibility = "visible";
|
|
101017
|
+
}
|
|
101018
|
+
} else {
|
|
101019
|
+
video.style.removeProperty("display");
|
|
101020
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
101021
|
+
video.style.setProperty("opacity", "0", "important");
|
|
101022
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
101023
|
+
if (hasImg) {
|
|
101024
|
+
img.style.visibility = "hidden";
|
|
101025
|
+
}
|
|
100999
101026
|
}
|
|
101000
101027
|
}
|
|
101001
101028
|
}, activeVideoIds);
|
|
@@ -101071,6 +101098,15 @@ function isFontResourceError(type, text, locationUrl) {
|
|
|
101071
101098
|
`${locationUrl} ${text}`
|
|
101072
101099
|
);
|
|
101073
101100
|
}
|
|
101101
|
+
async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
|
|
101102
|
+
const deadline = Date.now() + timeoutMs;
|
|
101103
|
+
while (Date.now() < deadline) {
|
|
101104
|
+
const ready = Boolean(await page.evaluate(expression));
|
|
101105
|
+
if (ready) return true;
|
|
101106
|
+
await new Promise((resolve13) => setTimeout(resolve13, intervalMs));
|
|
101107
|
+
}
|
|
101108
|
+
return Boolean(await page.evaluate(expression));
|
|
101109
|
+
}
|
|
101074
101110
|
async function initializeSession(session) {
|
|
101075
101111
|
const { page, serverUrl } = session;
|
|
101076
101112
|
page.on("console", (msg) => {
|
|
@@ -101100,14 +101136,26 @@ async function initializeSession(session) {
|
|
|
101100
101136
|
if (session.captureMode === "screenshot") {
|
|
101101
101137
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
101102
101138
|
const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
|
|
101103
|
-
await
|
|
101139
|
+
const pageReady2 = await pollPageExpression(
|
|
101140
|
+
page,
|
|
101104
101141
|
`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
|
|
101105
|
-
|
|
101142
|
+
pageReadyTimeout2
|
|
101106
101143
|
);
|
|
101107
|
-
|
|
101144
|
+
if (!pageReady2) {
|
|
101145
|
+
throw new Error(
|
|
101146
|
+
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
101147
|
+
);
|
|
101148
|
+
}
|
|
101149
|
+
const videosReady = await pollPageExpression(
|
|
101150
|
+
page,
|
|
101108
101151
|
`document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
|
|
101109
|
-
|
|
101152
|
+
pageReadyTimeout2
|
|
101110
101153
|
);
|
|
101154
|
+
if (!videosReady) {
|
|
101155
|
+
throw new Error(
|
|
101156
|
+
`[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
|
|
101157
|
+
);
|
|
101158
|
+
}
|
|
101111
101159
|
await page.evaluate(`document.fonts?.ready`);
|
|
101112
101160
|
session.isInitialized = true;
|
|
101113
101161
|
return;
|
|
@@ -101431,7 +101479,7 @@ var ENCODER_PRESETS = {
|
|
|
101431
101479
|
standard: { preset: "medium", quality: 18, codec: "h264" },
|
|
101432
101480
|
high: { preset: "slow", quality: 15, codec: "h264" }
|
|
101433
101481
|
};
|
|
101434
|
-
function getEncoderPreset(quality, format3 = "mp4") {
|
|
101482
|
+
function getEncoderPreset(quality, format3 = "mp4", hdr) {
|
|
101435
101483
|
const base = ENCODER_PRESETS[quality];
|
|
101436
101484
|
if (format3 === "webm") {
|
|
101437
101485
|
return {
|
|
@@ -101449,6 +101497,15 @@ function getEncoderPreset(quality, format3 = "mp4") {
|
|
|
101449
101497
|
pixelFormat: "yuva444p10le"
|
|
101450
101498
|
};
|
|
101451
101499
|
}
|
|
101500
|
+
if (hdr) {
|
|
101501
|
+
return {
|
|
101502
|
+
preset: base.preset === "ultrafast" ? "fast" : base.preset,
|
|
101503
|
+
quality: base.quality,
|
|
101504
|
+
codec: "h265",
|
|
101505
|
+
pixelFormat: "yuv420p10le",
|
|
101506
|
+
hdr
|
|
101507
|
+
};
|
|
101508
|
+
}
|
|
101452
101509
|
return { ...base, pixelFormat: "yuv420p" };
|
|
101453
101510
|
}
|
|
101454
101511
|
function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
@@ -101506,6 +101563,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
101506
101563
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
101507
101564
|
}
|
|
101508
101565
|
}
|
|
101566
|
+
if (codec === "h265") {
|
|
101567
|
+
args.push("-tag:v", "hvc1");
|
|
101568
|
+
}
|
|
101509
101569
|
} else if (codec === "vp9") {
|
|
101510
101570
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
101511
101571
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
@@ -101812,31 +101872,79 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
|
|
|
101812
101872
|
import { spawn as spawn6 } from "child_process";
|
|
101813
101873
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync4 } from "fs";
|
|
101814
101874
|
import { dirname as dirname6 } from "path";
|
|
101875
|
+
|
|
101876
|
+
// ../engine/src/utils/hdr.ts
|
|
101877
|
+
function isHdrColorSpace(cs) {
|
|
101878
|
+
if (!cs) return false;
|
|
101879
|
+
return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
|
|
101880
|
+
}
|
|
101881
|
+
var DEFAULT_HDR10_MASTERING = {
|
|
101882
|
+
masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
|
|
101883
|
+
maxCll: "1000,400"
|
|
101884
|
+
};
|
|
101885
|
+
function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
|
|
101886
|
+
const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
|
|
101887
|
+
const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
|
|
101888
|
+
const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
|
|
101889
|
+
return {
|
|
101890
|
+
colorPrimaries: "bt2020",
|
|
101891
|
+
colorTrc,
|
|
101892
|
+
colorspace: "bt2020nc",
|
|
101893
|
+
pixelFormat: "yuv420p10le",
|
|
101894
|
+
x265ColorParams: `${tagging}:${metadata}`,
|
|
101895
|
+
mastering
|
|
101896
|
+
};
|
|
101897
|
+
}
|
|
101898
|
+
function analyzeCompositionHdr(colorSpaces) {
|
|
101899
|
+
let hasPq = false;
|
|
101900
|
+
let hasHdr = false;
|
|
101901
|
+
for (const cs of colorSpaces) {
|
|
101902
|
+
if (!isHdrColorSpace(cs)) continue;
|
|
101903
|
+
hasHdr = true;
|
|
101904
|
+
if (cs?.colorTransfer === "smpte2084") hasPq = true;
|
|
101905
|
+
}
|
|
101906
|
+
if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
|
|
101907
|
+
const dominantTransfer = hasPq ? "pq" : "hlg";
|
|
101908
|
+
return { hasHdr: true, dominantTransfer };
|
|
101909
|
+
}
|
|
101910
|
+
|
|
101911
|
+
// ../engine/src/services/streamingEncoder.ts
|
|
101815
101912
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
101816
|
-
let
|
|
101817
|
-
|
|
101818
|
-
const
|
|
101819
|
-
|
|
101820
|
-
|
|
101821
|
-
|
|
101822
|
-
|
|
101823
|
-
|
|
101913
|
+
let cursor = startFrame;
|
|
101914
|
+
const pending = /* @__PURE__ */ new Map();
|
|
101915
|
+
const enqueueAt = (frame, resolve13) => {
|
|
101916
|
+
const list = pending.get(frame);
|
|
101917
|
+
if (list === void 0) {
|
|
101918
|
+
pending.set(frame, [resolve13]);
|
|
101919
|
+
} else {
|
|
101920
|
+
list.push(resolve13);
|
|
101824
101921
|
}
|
|
101825
101922
|
};
|
|
101826
|
-
|
|
101827
|
-
|
|
101828
|
-
|
|
101829
|
-
|
|
101830
|
-
|
|
101831
|
-
advanceTo: (frame) => {
|
|
101832
|
-
nextFrame = frame;
|
|
101833
|
-
resolveWaiters();
|
|
101834
|
-
},
|
|
101835
|
-
waitForAllDone: () => new Promise((resolve13) => {
|
|
101836
|
-
waiters.push({ frame: endFrame, resolve: resolve13 });
|
|
101837
|
-
resolveWaiters();
|
|
101838
|
-
})
|
|
101923
|
+
const flushAt = (frame) => {
|
|
101924
|
+
const list = pending.get(frame);
|
|
101925
|
+
if (list === void 0) return;
|
|
101926
|
+
pending.delete(frame);
|
|
101927
|
+
for (const resolve13 of list) resolve13();
|
|
101839
101928
|
};
|
|
101929
|
+
const waitForFrame = (frame) => new Promise((resolve13) => {
|
|
101930
|
+
if (frame === cursor) {
|
|
101931
|
+
resolve13();
|
|
101932
|
+
return;
|
|
101933
|
+
}
|
|
101934
|
+
enqueueAt(frame, resolve13);
|
|
101935
|
+
});
|
|
101936
|
+
const advanceTo = (frame) => {
|
|
101937
|
+
cursor = frame;
|
|
101938
|
+
flushAt(frame);
|
|
101939
|
+
};
|
|
101940
|
+
const waitForAllDone = () => new Promise((resolve13) => {
|
|
101941
|
+
if (cursor >= endFrame) {
|
|
101942
|
+
resolve13();
|
|
101943
|
+
return;
|
|
101944
|
+
}
|
|
101945
|
+
enqueueAt(endFrame, resolve13);
|
|
101946
|
+
});
|
|
101947
|
+
return { waitForFrame, advanceTo, waitForAllDone };
|
|
101840
101948
|
}
|
|
101841
101949
|
function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
101842
101950
|
const {
|
|
@@ -101849,19 +101957,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
101849
101957
|
useGpu = false,
|
|
101850
101958
|
imageFormat = "jpeg"
|
|
101851
101959
|
} = options;
|
|
101852
|
-
const
|
|
101853
|
-
|
|
101854
|
-
|
|
101855
|
-
"
|
|
101856
|
-
|
|
101857
|
-
|
|
101858
|
-
|
|
101859
|
-
|
|
101860
|
-
|
|
101861
|
-
|
|
101862
|
-
|
|
101863
|
-
|
|
101864
|
-
|
|
101960
|
+
const args = [];
|
|
101961
|
+
if (options.rawInputFormat) {
|
|
101962
|
+
const hdrTransfer = options.hdr?.transfer;
|
|
101963
|
+
const inputColorTrc = hdrTransfer === "pq" ? "smpte2084" : hdrTransfer === "hlg" ? "arib-std-b67" : void 0;
|
|
101964
|
+
args.push(
|
|
101965
|
+
"-f",
|
|
101966
|
+
"rawvideo",
|
|
101967
|
+
"-pix_fmt",
|
|
101968
|
+
options.rawInputFormat,
|
|
101969
|
+
"-s",
|
|
101970
|
+
`${options.width}x${options.height}`,
|
|
101971
|
+
"-framerate",
|
|
101972
|
+
String(fps)
|
|
101973
|
+
);
|
|
101974
|
+
if (inputColorTrc) {
|
|
101975
|
+
args.push(
|
|
101976
|
+
"-color_primaries",
|
|
101977
|
+
"bt2020",
|
|
101978
|
+
"-color_trc",
|
|
101979
|
+
inputColorTrc,
|
|
101980
|
+
"-colorspace",
|
|
101981
|
+
"bt2020nc"
|
|
101982
|
+
);
|
|
101983
|
+
}
|
|
101984
|
+
args.push("-i", "-");
|
|
101985
|
+
} else {
|
|
101986
|
+
const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
|
|
101987
|
+
args.push("-f", "image2pipe", "-vcodec", inputCodec, "-framerate", String(fps), "-i", "-");
|
|
101988
|
+
}
|
|
101989
|
+
args.push("-r", String(fps));
|
|
101865
101990
|
const shouldUseGpu = useGpu && gpuEncoder !== null;
|
|
101866
101991
|
if (codec === "h264" || codec === "h265") {
|
|
101867
101992
|
if (shouldUseGpu) {
|
|
@@ -101899,12 +102024,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
101899
102024
|
if (bitrate) args.push("-b:v", bitrate);
|
|
101900
102025
|
else args.push("-crf", String(quality));
|
|
101901
102026
|
const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
|
|
101902
|
-
const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
102027
|
+
const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
101903
102028
|
if (preset === "ultrafast") {
|
|
101904
102029
|
args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
|
|
101905
102030
|
} else {
|
|
101906
102031
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
101907
102032
|
}
|
|
102033
|
+
if (codec === "h265") {
|
|
102034
|
+
args.push("-tag:v", "hvc1");
|
|
102035
|
+
}
|
|
101908
102036
|
}
|
|
101909
102037
|
} else if (codec === "vp9") {
|
|
101910
102038
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
@@ -101920,17 +102048,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
101920
102048
|
return [...args, "-y", outputPath];
|
|
101921
102049
|
}
|
|
101922
102050
|
if (codec === "h264" || codec === "h265") {
|
|
101923
|
-
|
|
101924
|
-
|
|
101925
|
-
|
|
101926
|
-
|
|
101927
|
-
|
|
101928
|
-
|
|
101929
|
-
|
|
101930
|
-
|
|
101931
|
-
|
|
101932
|
-
|
|
101933
|
-
|
|
102051
|
+
if (options.rawInputFormat && options.hdr) {
|
|
102052
|
+
args.push(
|
|
102053
|
+
"-colorspace:v",
|
|
102054
|
+
"bt2020nc",
|
|
102055
|
+
"-color_primaries:v",
|
|
102056
|
+
"bt2020",
|
|
102057
|
+
"-color_trc:v",
|
|
102058
|
+
options.hdr.transfer === "pq" ? "smpte2084" : "arib-std-b67",
|
|
102059
|
+
"-color_range",
|
|
102060
|
+
"tv"
|
|
102061
|
+
);
|
|
102062
|
+
} else {
|
|
102063
|
+
args.push(
|
|
102064
|
+
"-colorspace:v",
|
|
102065
|
+
"bt709",
|
|
102066
|
+
"-color_primaries:v",
|
|
102067
|
+
"bt709",
|
|
102068
|
+
"-color_trc:v",
|
|
102069
|
+
"bt709",
|
|
102070
|
+
"-color_range",
|
|
102071
|
+
"tv"
|
|
102072
|
+
);
|
|
102073
|
+
}
|
|
102074
|
+
if (options.rawInputFormat) {
|
|
102075
|
+
} else if (gpuEncoder === "vaapi") {
|
|
101934
102076
|
const vfIdx = args.indexOf("-vf");
|
|
101935
102077
|
if (vfIdx !== -1) {
|
|
101936
102078
|
args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
|
|
@@ -102000,14 +102142,16 @@ Process error: ${err.message}`;
|
|
|
102000
102142
|
if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
|
|
102001
102143
|
return false;
|
|
102002
102144
|
}
|
|
102003
|
-
|
|
102145
|
+
const copy = Buffer.from(buffer);
|
|
102146
|
+
return ffmpeg.stdin.write(copy);
|
|
102004
102147
|
},
|
|
102005
102148
|
close: async () => {
|
|
102006
102149
|
clearTimeout(timer2);
|
|
102007
102150
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
102008
|
-
|
|
102151
|
+
const stdin = ffmpeg.stdin;
|
|
102152
|
+
if (stdin && !stdin.destroyed) {
|
|
102009
102153
|
await new Promise((resolve13) => {
|
|
102010
|
-
|
|
102154
|
+
stdin.end(() => resolve13());
|
|
102011
102155
|
});
|
|
102012
102156
|
}
|
|
102013
102157
|
await exitPromise;
|
|
@@ -102111,6 +102255,10 @@ async function extractVideoMetadata(filePath) {
|
|
|
102111
102255
|
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
102112
102256
|
const fps = avgFps || rFps;
|
|
102113
102257
|
const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
|
|
102258
|
+
const colorTransfer = videoStream.color_transfer || "";
|
|
102259
|
+
const colorPrimaries = videoStream.color_primaries || "";
|
|
102260
|
+
const colorSpaceVal = videoStream.color_space || "";
|
|
102261
|
+
const hasColorInfo = !!(colorTransfer || colorPrimaries || colorSpaceVal);
|
|
102114
102262
|
return {
|
|
102115
102263
|
durationSeconds: output2.format.duration ? parseFloat(output2.format.duration) : 0,
|
|
102116
102264
|
width: videoStream.width || 0,
|
|
@@ -102118,7 +102266,8 @@ async function extractVideoMetadata(filePath) {
|
|
|
102118
102266
|
fps,
|
|
102119
102267
|
videoCodec: videoStream.codec_name || "unknown",
|
|
102120
102268
|
hasAudio: output2.streams.some((s) => s.codec_type === "audio"),
|
|
102121
|
-
isVFR
|
|
102269
|
+
isVFR,
|
|
102270
|
+
colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
|
|
102122
102271
|
};
|
|
102123
102272
|
})();
|
|
102124
102273
|
videoMetadataCache.set(filePath, probePromise);
|
|
@@ -102326,18 +102475,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
102326
102475
|
const metadata = await extractVideoMetadata(videoPath);
|
|
102327
102476
|
const framePattern = `frame_%05d.${format3}`;
|
|
102328
102477
|
const outputPattern = join8(videoOutputDir, framePattern);
|
|
102329
|
-
const
|
|
102330
|
-
|
|
102331
|
-
|
|
102332
|
-
|
|
102333
|
-
|
|
102334
|
-
|
|
102335
|
-
|
|
102336
|
-
|
|
102337
|
-
|
|
102338
|
-
"
|
|
102339
|
-
|
|
102340
|
-
|
|
102478
|
+
const isHdr = isHdrColorSpace(metadata.colorSpace);
|
|
102479
|
+
const isMacOS = process.platform === "darwin";
|
|
102480
|
+
const args = [];
|
|
102481
|
+
if (isHdr && isMacOS) {
|
|
102482
|
+
args.push("-hwaccel", "videotoolbox");
|
|
102483
|
+
}
|
|
102484
|
+
args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
|
|
102485
|
+
const vfFilters = [];
|
|
102486
|
+
if (isHdr && isMacOS) {
|
|
102487
|
+
vfFilters.push("format=nv12");
|
|
102488
|
+
}
|
|
102489
|
+
vfFilters.push(`fps=${fps}`);
|
|
102490
|
+
args.push("-vf", vfFilters.join(","));
|
|
102491
|
+
args.push("-q:v", format3 === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0");
|
|
102341
102492
|
if (format3 === "png") args.push("-compression_level", "6");
|
|
102342
102493
|
args.push("-y", outputPattern);
|
|
102343
102494
|
return new Promise((resolve13, reject) => {
|
|
@@ -102397,30 +102548,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
102397
102548
|
});
|
|
102398
102549
|
});
|
|
102399
102550
|
}
|
|
102551
|
+
async function convertSdrToHdr(inputPath, outputPath, signal, config2) {
|
|
102552
|
+
const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
102553
|
+
const args = [
|
|
102554
|
+
"-i",
|
|
102555
|
+
inputPath,
|
|
102556
|
+
"-vf",
|
|
102557
|
+
"colorspace=all=bt2020:iall=bt709:range=tv",
|
|
102558
|
+
"-color_primaries",
|
|
102559
|
+
"bt2020",
|
|
102560
|
+
"-color_trc",
|
|
102561
|
+
"arib-std-b67",
|
|
102562
|
+
"-colorspace",
|
|
102563
|
+
"bt2020nc",
|
|
102564
|
+
"-c:v",
|
|
102565
|
+
"libx264",
|
|
102566
|
+
"-preset",
|
|
102567
|
+
"fast",
|
|
102568
|
+
"-crf",
|
|
102569
|
+
"16",
|
|
102570
|
+
"-c:a",
|
|
102571
|
+
"copy",
|
|
102572
|
+
"-y",
|
|
102573
|
+
outputPath
|
|
102574
|
+
];
|
|
102575
|
+
const result = await runFfmpeg(args, { signal, timeout: timeout2 });
|
|
102576
|
+
if (!result.success) {
|
|
102577
|
+
throw new Error(
|
|
102578
|
+
`SDR\u2192HDR conversion failed (exit ${result.exitCode}): ${result.stderr.slice(-300)}`
|
|
102579
|
+
);
|
|
102580
|
+
}
|
|
102581
|
+
}
|
|
102400
102582
|
async function extractAllVideoFrames(videos, baseDir, options, signal, config2, compiledDir) {
|
|
102401
102583
|
const startTime = Date.now();
|
|
102402
102584
|
const extracted = [];
|
|
102403
102585
|
const errors = [];
|
|
102404
102586
|
let totalFramesExtracted = 0;
|
|
102587
|
+
const resolvedVideos = [];
|
|
102588
|
+
for (const video of videos) {
|
|
102589
|
+
if (signal?.aborted) break;
|
|
102590
|
+
try {
|
|
102591
|
+
let videoPath = video.src;
|
|
102592
|
+
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
102593
|
+
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
102594
|
+
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
102595
|
+
}
|
|
102596
|
+
if (isHttpUrl(videoPath)) {
|
|
102597
|
+
const downloadDir = join8(options.outputDir, "_downloads");
|
|
102598
|
+
mkdirSync5(downloadDir, { recursive: true });
|
|
102599
|
+
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
102600
|
+
}
|
|
102601
|
+
if (!existsSync8(videoPath)) {
|
|
102602
|
+
errors.push({ videoId: video.id, error: `Video file not found: ${videoPath}` });
|
|
102603
|
+
continue;
|
|
102604
|
+
}
|
|
102605
|
+
resolvedVideos.push({ video, videoPath });
|
|
102606
|
+
} catch (err) {
|
|
102607
|
+
errors.push({ videoId: video.id, error: err instanceof Error ? err.message : String(err) });
|
|
102608
|
+
}
|
|
102609
|
+
}
|
|
102610
|
+
const videoColorSpaces = await Promise.all(
|
|
102611
|
+
resolvedVideos.map(async ({ videoPath }) => {
|
|
102612
|
+
const metadata = await extractVideoMetadata(videoPath);
|
|
102613
|
+
return metadata.colorSpace;
|
|
102614
|
+
})
|
|
102615
|
+
);
|
|
102616
|
+
const hasAnyHdr = videoColorSpaces.some(isHdrColorSpace);
|
|
102617
|
+
if (hasAnyHdr) {
|
|
102618
|
+
const convertDir = join8(options.outputDir, "_hdr_normalized");
|
|
102619
|
+
mkdirSync5(convertDir, { recursive: true });
|
|
102620
|
+
for (let i = 0; i < resolvedVideos.length; i++) {
|
|
102621
|
+
if (signal?.aborted) break;
|
|
102622
|
+
const cs = videoColorSpaces[i] ?? null;
|
|
102623
|
+
if (!isHdrColorSpace(cs)) {
|
|
102624
|
+
const entry = resolvedVideos[i];
|
|
102625
|
+
if (!entry) continue;
|
|
102626
|
+
const convertedPath = join8(convertDir, `${entry.video.id}_hdr.mp4`);
|
|
102627
|
+
try {
|
|
102628
|
+
await convertSdrToHdr(entry.videoPath, convertedPath, signal, config2);
|
|
102629
|
+
entry.videoPath = convertedPath;
|
|
102630
|
+
} catch (err) {
|
|
102631
|
+
errors.push({
|
|
102632
|
+
videoId: entry.video.id,
|
|
102633
|
+
error: `SDR\u2192HDR conversion failed: ${err instanceof Error ? err.message : String(err)}`
|
|
102634
|
+
});
|
|
102635
|
+
}
|
|
102636
|
+
}
|
|
102637
|
+
}
|
|
102638
|
+
}
|
|
102405
102639
|
const results = await Promise.all(
|
|
102406
|
-
|
|
102640
|
+
resolvedVideos.map(async ({ video, videoPath }) => {
|
|
102407
102641
|
if (signal?.aborted) {
|
|
102408
102642
|
throw new Error("Video frame extraction cancelled");
|
|
102409
102643
|
}
|
|
102410
102644
|
try {
|
|
102411
|
-
let videoPath = video.src;
|
|
102412
|
-
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
102413
|
-
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
102414
|
-
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
102415
|
-
}
|
|
102416
|
-
if (isHttpUrl(videoPath)) {
|
|
102417
|
-
const downloadDir = join8(options.outputDir, "_downloads");
|
|
102418
|
-
mkdirSync5(downloadDir, { recursive: true });
|
|
102419
|
-
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
102420
|
-
}
|
|
102421
|
-
if (!existsSync8(videoPath)) {
|
|
102422
|
-
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
102423
|
-
}
|
|
102424
102645
|
let videoDuration = video.end - video.start;
|
|
102425
102646
|
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
|
|
102426
102647
|
const metadata = await extractVideoMetadata(videoPath);
|
|
@@ -102655,6 +102876,74 @@ function createVideoFrameInjector(frameLookup, config2) {
|
|
|
102655
102876
|
}
|
|
102656
102877
|
};
|
|
102657
102878
|
}
|
|
102879
|
+
async function hideVideoElements(page, videoIds) {
|
|
102880
|
+
if (videoIds.length === 0) return;
|
|
102881
|
+
await page.evaluate((ids) => {
|
|
102882
|
+
for (const id of ids) {
|
|
102883
|
+
const el = document.getElementById(id);
|
|
102884
|
+
if (el) {
|
|
102885
|
+
el.style.setProperty("visibility", "hidden", "important");
|
|
102886
|
+
el.style.setProperty("opacity", "0", "important");
|
|
102887
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
102888
|
+
if (img) img.style.setProperty("visibility", "hidden", "important");
|
|
102889
|
+
}
|
|
102890
|
+
}
|
|
102891
|
+
}, videoIds);
|
|
102892
|
+
}
|
|
102893
|
+
async function showVideoElements(page, videoIds) {
|
|
102894
|
+
if (videoIds.length === 0) return;
|
|
102895
|
+
await page.evaluate((ids) => {
|
|
102896
|
+
for (const id of ids) {
|
|
102897
|
+
const el = document.getElementById(id);
|
|
102898
|
+
if (el) {
|
|
102899
|
+
el.style.removeProperty("visibility");
|
|
102900
|
+
el.style.removeProperty("opacity");
|
|
102901
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
102902
|
+
if (img) img.style.removeProperty("visibility");
|
|
102903
|
+
}
|
|
102904
|
+
}
|
|
102905
|
+
}, videoIds);
|
|
102906
|
+
}
|
|
102907
|
+
async function queryElementStacking(page, nativeHdrVideoIds) {
|
|
102908
|
+
const hdrIds = Array.from(nativeHdrVideoIds);
|
|
102909
|
+
return page.evaluate((hdrIdList) => {
|
|
102910
|
+
const hdrSet = new Set(hdrIdList);
|
|
102911
|
+
const elements = document.querySelectorAll("[data-start]");
|
|
102912
|
+
const results = [];
|
|
102913
|
+
function getEffectiveZIndex(node) {
|
|
102914
|
+
let current = node;
|
|
102915
|
+
while (current) {
|
|
102916
|
+
const cs = window.getComputedStyle(current);
|
|
102917
|
+
const pos = cs.position;
|
|
102918
|
+
const z = parseInt(cs.zIndex);
|
|
102919
|
+
if (!Number.isNaN(z) && pos !== "static") return z;
|
|
102920
|
+
current = current.parentElement;
|
|
102921
|
+
}
|
|
102922
|
+
return 0;
|
|
102923
|
+
}
|
|
102924
|
+
for (const el of elements) {
|
|
102925
|
+
const id = el.id;
|
|
102926
|
+
if (!id) continue;
|
|
102927
|
+
const rect = el.getBoundingClientRect();
|
|
102928
|
+
const style = window.getComputedStyle(el);
|
|
102929
|
+
const zIndex = getEffectiveZIndex(el);
|
|
102930
|
+
const opacity = parseFloat(style.opacity) || 1;
|
|
102931
|
+
const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
102932
|
+
results.push({
|
|
102933
|
+
id,
|
|
102934
|
+
zIndex,
|
|
102935
|
+
x: Math.round(rect.x),
|
|
102936
|
+
y: Math.round(rect.y),
|
|
102937
|
+
width: Math.round(rect.width),
|
|
102938
|
+
height: Math.round(rect.height),
|
|
102939
|
+
opacity,
|
|
102940
|
+
visible,
|
|
102941
|
+
isHdr: hdrSet.has(id)
|
|
102942
|
+
});
|
|
102943
|
+
}
|
|
102944
|
+
return results;
|
|
102945
|
+
}, hdrIds);
|
|
102946
|
+
}
|
|
102658
102947
|
|
|
102659
102948
|
// ../engine/src/services/audioMixer.ts
|
|
102660
102949
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
@@ -105765,6 +106054,292 @@ var serve = (options, listeningListener) => {
|
|
|
105765
106054
|
return server;
|
|
105766
106055
|
};
|
|
105767
106056
|
|
|
106057
|
+
// ../engine/src/utils/alphaBlit.ts
|
|
106058
|
+
import { inflateSync } from "zlib";
|
|
106059
|
+
function paeth(a, b, c) {
|
|
106060
|
+
const p = a + b - c;
|
|
106061
|
+
const pa = Math.abs(p - a);
|
|
106062
|
+
const pb = Math.abs(p - b);
|
|
106063
|
+
const pc = Math.abs(p - c);
|
|
106064
|
+
if (pa <= pb && pa <= pc) return a;
|
|
106065
|
+
if (pb <= pc) return b;
|
|
106066
|
+
return c;
|
|
106067
|
+
}
|
|
106068
|
+
function decodePngRaw(buf, caller) {
|
|
106069
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
|
|
106070
|
+
throw new Error(`${caller}: not a PNG file`);
|
|
106071
|
+
}
|
|
106072
|
+
let pos = 8;
|
|
106073
|
+
let width = 0;
|
|
106074
|
+
let height = 0;
|
|
106075
|
+
let bitDepth = 0;
|
|
106076
|
+
let colorType = 0;
|
|
106077
|
+
let interlace = 0;
|
|
106078
|
+
let sawIhdr = false;
|
|
106079
|
+
const idatChunks = [];
|
|
106080
|
+
while (pos + 12 <= buf.length) {
|
|
106081
|
+
const chunkLen = buf.readUInt32BE(pos);
|
|
106082
|
+
const chunkType = buf.toString("ascii", pos + 4, pos + 8);
|
|
106083
|
+
const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
|
|
106084
|
+
if (chunkType === "IHDR") {
|
|
106085
|
+
width = chunkData.readUInt32BE(0);
|
|
106086
|
+
height = chunkData.readUInt32BE(4);
|
|
106087
|
+
bitDepth = chunkData[8] ?? 0;
|
|
106088
|
+
colorType = chunkData[9] ?? 0;
|
|
106089
|
+
interlace = chunkData[12] ?? 0;
|
|
106090
|
+
sawIhdr = true;
|
|
106091
|
+
} else if (chunkType === "IDAT") {
|
|
106092
|
+
idatChunks.push(Buffer.from(chunkData));
|
|
106093
|
+
} else if (chunkType === "IEND") {
|
|
106094
|
+
break;
|
|
106095
|
+
}
|
|
106096
|
+
pos += 12 + chunkLen;
|
|
106097
|
+
}
|
|
106098
|
+
if (!sawIhdr) {
|
|
106099
|
+
throw new Error(`${caller}: PNG missing IHDR chunk`);
|
|
106100
|
+
}
|
|
106101
|
+
if (colorType !== 2 && colorType !== 6) {
|
|
106102
|
+
throw new Error(`${caller}: unsupported color type ${colorType} (expected 2=RGB or 6=RGBA)`);
|
|
106103
|
+
}
|
|
106104
|
+
if (interlace !== 0) {
|
|
106105
|
+
throw new Error(
|
|
106106
|
+
`${caller}: Adam7-interlaced PNGs are not supported (interlace method ${interlace})`
|
|
106107
|
+
);
|
|
106108
|
+
}
|
|
106109
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
106110
|
+
const bpp = channels * (bitDepth / 8);
|
|
106111
|
+
const stride = width * bpp;
|
|
106112
|
+
const compressed = Buffer.concat(idatChunks);
|
|
106113
|
+
const decompressed = inflateSync(compressed);
|
|
106114
|
+
const rawPixels = Buffer.allocUnsafe(height * stride);
|
|
106115
|
+
const prevRow = new Uint8Array(stride);
|
|
106116
|
+
const currRow = new Uint8Array(stride);
|
|
106117
|
+
let srcPos = 0;
|
|
106118
|
+
for (let y = 0; y < height; y++) {
|
|
106119
|
+
const filterType = decompressed[srcPos++] ?? 0;
|
|
106120
|
+
const rawRow = decompressed.subarray(srcPos, srcPos + stride);
|
|
106121
|
+
srcPos += stride;
|
|
106122
|
+
switch (filterType) {
|
|
106123
|
+
case 0:
|
|
106124
|
+
currRow.set(rawRow);
|
|
106125
|
+
break;
|
|
106126
|
+
case 1:
|
|
106127
|
+
for (let x = 0; x < stride; x++) {
|
|
106128
|
+
currRow[x] = (rawRow[x] ?? 0) + (x >= bpp ? currRow[x - bpp] ?? 0 : 0) & 255;
|
|
106129
|
+
}
|
|
106130
|
+
break;
|
|
106131
|
+
case 2:
|
|
106132
|
+
for (let x = 0; x < stride; x++) {
|
|
106133
|
+
currRow[x] = (rawRow[x] ?? 0) + (prevRow[x] ?? 0) & 255;
|
|
106134
|
+
}
|
|
106135
|
+
break;
|
|
106136
|
+
case 3:
|
|
106137
|
+
for (let x = 0; x < stride; x++) {
|
|
106138
|
+
const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
|
|
106139
|
+
const up = prevRow[x] ?? 0;
|
|
106140
|
+
currRow[x] = (rawRow[x] ?? 0) + Math.floor((left2 + up) / 2) & 255;
|
|
106141
|
+
}
|
|
106142
|
+
break;
|
|
106143
|
+
case 4:
|
|
106144
|
+
for (let x = 0; x < stride; x++) {
|
|
106145
|
+
const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
|
|
106146
|
+
const up = prevRow[x] ?? 0;
|
|
106147
|
+
const upLeft = x >= bpp ? prevRow[x - bpp] ?? 0 : 0;
|
|
106148
|
+
currRow[x] = (rawRow[x] ?? 0) + paeth(left2, up, upLeft) & 255;
|
|
106149
|
+
}
|
|
106150
|
+
break;
|
|
106151
|
+
default:
|
|
106152
|
+
throw new Error(`${caller}: unknown filter type ${filterType} at row ${y}`);
|
|
106153
|
+
}
|
|
106154
|
+
rawPixels.set(currRow, y * stride);
|
|
106155
|
+
prevRow.set(currRow);
|
|
106156
|
+
}
|
|
106157
|
+
return { width, height, bitDepth, colorType, rawPixels };
|
|
106158
|
+
}
|
|
106159
|
+
function decodePng(buf) {
|
|
106160
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePng");
|
|
106161
|
+
if (bitDepth !== 8) {
|
|
106162
|
+
throw new Error(`decodePng: unsupported bit depth ${bitDepth} (expected 8)`);
|
|
106163
|
+
}
|
|
106164
|
+
const output2 = new Uint8Array(width * height * 4);
|
|
106165
|
+
if (colorType === 6) {
|
|
106166
|
+
output2.set(rawPixels);
|
|
106167
|
+
} else {
|
|
106168
|
+
for (let i = 0; i < width * height; i++) {
|
|
106169
|
+
output2[i * 4 + 0] = rawPixels[i * 3 + 0] ?? 0;
|
|
106170
|
+
output2[i * 4 + 1] = rawPixels[i * 3 + 1] ?? 0;
|
|
106171
|
+
output2[i * 4 + 2] = rawPixels[i * 3 + 2] ?? 0;
|
|
106172
|
+
output2[i * 4 + 3] = 255;
|
|
106173
|
+
}
|
|
106174
|
+
}
|
|
106175
|
+
return { width, height, data: output2 };
|
|
106176
|
+
}
|
|
106177
|
+
function decodePngToRgb48le(buf) {
|
|
106178
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePngToRgb48le");
|
|
106179
|
+
if (bitDepth !== 16) {
|
|
106180
|
+
throw new Error(`decodePngToRgb48le: unsupported bit depth ${bitDepth} (expected 16)`);
|
|
106181
|
+
}
|
|
106182
|
+
const bpp = colorType === 6 ? 8 : 6;
|
|
106183
|
+
const output2 = Buffer.allocUnsafe(width * height * 6);
|
|
106184
|
+
for (let y = 0; y < height; y++) {
|
|
106185
|
+
const dstBase = y * width * 6;
|
|
106186
|
+
const srcRowBase = y * width * bpp;
|
|
106187
|
+
for (let x = 0; x < width; x++) {
|
|
106188
|
+
const srcBase = srcRowBase + x * bpp;
|
|
106189
|
+
output2[dstBase + x * 6 + 0] = rawPixels[srcBase + 1] ?? 0;
|
|
106190
|
+
output2[dstBase + x * 6 + 1] = rawPixels[srcBase + 0] ?? 0;
|
|
106191
|
+
output2[dstBase + x * 6 + 2] = rawPixels[srcBase + 3] ?? 0;
|
|
106192
|
+
output2[dstBase + x * 6 + 3] = rawPixels[srcBase + 2] ?? 0;
|
|
106193
|
+
output2[dstBase + x * 6 + 4] = rawPixels[srcBase + 5] ?? 0;
|
|
106194
|
+
output2[dstBase + x * 6 + 5] = rawPixels[srcBase + 4] ?? 0;
|
|
106195
|
+
}
|
|
106196
|
+
}
|
|
106197
|
+
return { width, height, data: output2 };
|
|
106198
|
+
}
|
|
106199
|
+
function buildSrgbToHdrLut(transfer) {
|
|
106200
|
+
const lut = new Uint16Array(256);
|
|
106201
|
+
const hlgA = 0.17883277;
|
|
106202
|
+
const hlgB = 1 - 4 * hlgA;
|
|
106203
|
+
const hlgC = 0.5 - hlgA * Math.log(4 * hlgA);
|
|
106204
|
+
const pqM1 = 0.1593017578125;
|
|
106205
|
+
const pqM2 = 78.84375;
|
|
106206
|
+
const pqC1 = 0.8359375;
|
|
106207
|
+
const pqC2 = 18.8515625;
|
|
106208
|
+
const pqC3 = 18.6875;
|
|
106209
|
+
const pqMaxNits = 1e4;
|
|
106210
|
+
const sdrNits = 203;
|
|
106211
|
+
for (let i = 0; i < 256; i++) {
|
|
106212
|
+
const v = i / 255;
|
|
106213
|
+
const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
106214
|
+
let signal;
|
|
106215
|
+
if (transfer === "hlg") {
|
|
106216
|
+
signal = linear <= 1 / 12 ? Math.sqrt(3 * linear) : hlgA * Math.log(12 * linear - hlgB) + hlgC;
|
|
106217
|
+
} else {
|
|
106218
|
+
const Lp = Math.max(0, linear * sdrNits / pqMaxNits);
|
|
106219
|
+
const Lm1 = Math.pow(Lp, pqM1);
|
|
106220
|
+
signal = Math.pow((pqC1 + pqC2 * Lm1) / (1 + pqC3 * Lm1), pqM2);
|
|
106221
|
+
}
|
|
106222
|
+
lut[i] = Math.min(65535, Math.round(signal * 65535));
|
|
106223
|
+
}
|
|
106224
|
+
return lut;
|
|
106225
|
+
}
|
|
106226
|
+
var SRGB_TO_HLG = buildSrgbToHdrLut("hlg");
|
|
106227
|
+
var SRGB_TO_PQ = buildSrgbToHdrLut("pq");
|
|
106228
|
+
function getSrgbToHdrLut(transfer) {
|
|
106229
|
+
return transfer === "pq" ? SRGB_TO_PQ : SRGB_TO_HLG;
|
|
106230
|
+
}
|
|
106231
|
+
function blitRgba8OverRgb48le(domRgba, canvas, width, height, transfer = "hlg") {
|
|
106232
|
+
const pixelCount = width * height;
|
|
106233
|
+
const lut = getSrgbToHdrLut(transfer);
|
|
106234
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
106235
|
+
const da = domRgba[i * 4 + 3] ?? 0;
|
|
106236
|
+
if (da === 0) {
|
|
106237
|
+
continue;
|
|
106238
|
+
} else if (da === 255) {
|
|
106239
|
+
const r16 = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
|
|
106240
|
+
const g16 = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
|
|
106241
|
+
const b16 = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
|
|
106242
|
+
canvas.writeUInt16LE(r16, i * 6);
|
|
106243
|
+
canvas.writeUInt16LE(g16, i * 6 + 2);
|
|
106244
|
+
canvas.writeUInt16LE(b16, i * 6 + 4);
|
|
106245
|
+
} else {
|
|
106246
|
+
const alpha = da / 255;
|
|
106247
|
+
const invAlpha = 1 - alpha;
|
|
106248
|
+
const hdrR = (canvas[i * 6 + 0] ?? 0) | (canvas[i * 6 + 1] ?? 0) << 8;
|
|
106249
|
+
const hdrG = (canvas[i * 6 + 2] ?? 0) | (canvas[i * 6 + 3] ?? 0) << 8;
|
|
106250
|
+
const hdrB = (canvas[i * 6 + 4] ?? 0) | (canvas[i * 6 + 5] ?? 0) << 8;
|
|
106251
|
+
const domR = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
|
|
106252
|
+
const domG = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
|
|
106253
|
+
const domB = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
|
|
106254
|
+
canvas.writeUInt16LE(Math.round(domR * alpha + hdrR * invAlpha), i * 6);
|
|
106255
|
+
canvas.writeUInt16LE(Math.round(domG * alpha + hdrG * invAlpha), i * 6 + 2);
|
|
106256
|
+
canvas.writeUInt16LE(Math.round(domB * alpha + hdrB * invAlpha), i * 6 + 4);
|
|
106257
|
+
}
|
|
106258
|
+
}
|
|
106259
|
+
}
|
|
106260
|
+
function cornerAlpha(px, py, cx, cy, r) {
|
|
106261
|
+
const dx = px - cx;
|
|
106262
|
+
const dy = py - cy;
|
|
106263
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
106264
|
+
if (dist > r + 0.5) return 0;
|
|
106265
|
+
if (dist > r - 0.5) return r + 0.5 - dist;
|
|
106266
|
+
return 1;
|
|
106267
|
+
}
|
|
106268
|
+
function roundedRectAlpha(px, py, w, h, radii) {
|
|
106269
|
+
const [tl, tr, br, bl] = radii;
|
|
106270
|
+
if (px < tl && py < tl) return cornerAlpha(px, py, tl, tl, tl);
|
|
106271
|
+
if (px >= w - tr && py < tr) return cornerAlpha(px, py, w - tr, tr, tr);
|
|
106272
|
+
if (px >= w - br && py >= h - br) return cornerAlpha(px, py, w - br, h - br, br);
|
|
106273
|
+
if (px < bl && py >= h - bl) return cornerAlpha(px, py, bl, h - bl, bl);
|
|
106274
|
+
return 1;
|
|
106275
|
+
}
|
|
106276
|
+
function blitRgb48leRegion(canvas, source2, dx, dy, sw, sh, canvasWidth, canvasHeight, opacity, borderRadius) {
|
|
106277
|
+
if (sw <= 0 || sh <= 0) return;
|
|
106278
|
+
const op = opacity ?? 1;
|
|
106279
|
+
const x0 = Math.max(0, dx);
|
|
106280
|
+
const y0 = Math.max(0, dy);
|
|
106281
|
+
const x1 = Math.min(canvasWidth, dx + sw);
|
|
106282
|
+
const y1 = Math.min(canvasHeight, dy + sh);
|
|
106283
|
+
if (x0 >= x1 || y0 >= y1) return;
|
|
106284
|
+
const clippedW = x1 - x0;
|
|
106285
|
+
const srcOffsetX = x0 - dx;
|
|
106286
|
+
const srcOffsetY = y0 - dy;
|
|
106287
|
+
const hasMask = borderRadius !== void 0;
|
|
106288
|
+
if (op >= 0.999 && !hasMask) {
|
|
106289
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
106290
|
+
const srcRowOff = ((srcOffsetY + y) * sw + srcOffsetX) * 6;
|
|
106291
|
+
const dstRowOff = ((y0 + y) * canvasWidth + x0) * 6;
|
|
106292
|
+
source2.copy(canvas, dstRowOff, srcRowOff, srcRowOff + clippedW * 6);
|
|
106293
|
+
}
|
|
106294
|
+
} else {
|
|
106295
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
106296
|
+
for (let x = 0; x < clippedW; x++) {
|
|
106297
|
+
let effectiveOp = op;
|
|
106298
|
+
if (hasMask) {
|
|
106299
|
+
const ma = roundedRectAlpha(srcOffsetX + x, srcOffsetY + y, sw, sh, borderRadius);
|
|
106300
|
+
if (ma <= 0) continue;
|
|
106301
|
+
effectiveOp *= ma;
|
|
106302
|
+
}
|
|
106303
|
+
const srcOff = ((srcOffsetY + y) * sw + srcOffsetX + x) * 6;
|
|
106304
|
+
const dstOff = ((y0 + y) * canvasWidth + x0 + x) * 6;
|
|
106305
|
+
if (effectiveOp >= 0.999) {
|
|
106306
|
+
source2.copy(canvas, dstOff, srcOff, srcOff + 6);
|
|
106307
|
+
} else {
|
|
106308
|
+
const invEff = 1 - effectiveOp;
|
|
106309
|
+
const sr = source2.readUInt16LE(srcOff);
|
|
106310
|
+
const sg = source2.readUInt16LE(srcOff + 2);
|
|
106311
|
+
const sb = source2.readUInt16LE(srcOff + 4);
|
|
106312
|
+
const dr = canvas.readUInt16LE(dstOff);
|
|
106313
|
+
const dg = canvas.readUInt16LE(dstOff + 2);
|
|
106314
|
+
const db = canvas.readUInt16LE(dstOff + 4);
|
|
106315
|
+
canvas.writeUInt16LE(Math.round(sr * effectiveOp + dr * invEff), dstOff);
|
|
106316
|
+
canvas.writeUInt16LE(Math.round(sg * effectiveOp + dg * invEff), dstOff + 2);
|
|
106317
|
+
canvas.writeUInt16LE(Math.round(sb * effectiveOp + db * invEff), dstOff + 4);
|
|
106318
|
+
}
|
|
106319
|
+
}
|
|
106320
|
+
}
|
|
106321
|
+
}
|
|
106322
|
+
}
|
|
106323
|
+
|
|
106324
|
+
// ../engine/src/utils/layerCompositor.ts
|
|
106325
|
+
function groupIntoLayers(elements) {
|
|
106326
|
+
const sorted = [...elements].sort((a, b) => a.zIndex - b.zIndex);
|
|
106327
|
+
const layers = [];
|
|
106328
|
+
for (const el of sorted) {
|
|
106329
|
+
if (el.isHdr) {
|
|
106330
|
+
layers.push({ type: "hdr", element: el });
|
|
106331
|
+
} else {
|
|
106332
|
+
const last2 = layers[layers.length - 1];
|
|
106333
|
+
if (last2 && last2.type === "dom") {
|
|
106334
|
+
last2.elementIds.push(el.id);
|
|
106335
|
+
} else {
|
|
106336
|
+
layers.push({ type: "dom", elementIds: [el.id] });
|
|
106337
|
+
}
|
|
106338
|
+
}
|
|
106339
|
+
}
|
|
106340
|
+
return layers;
|
|
106341
|
+
}
|
|
106342
|
+
|
|
105768
106343
|
// src/services/renderOrchestrator.ts
|
|
105769
106344
|
import { join as join15, dirname as dirname10, resolve as resolve10 } from "path";
|
|
105770
106345
|
import { randomUUID } from "crypto";
|
|
@@ -105871,6 +106446,102 @@ var MIME_TYPES = {
|
|
|
105871
106446
|
".ttf": "font/ttf",
|
|
105872
106447
|
".otf": "font/otf"
|
|
105873
106448
|
};
|
|
106449
|
+
var VIRTUAL_TIME_SHIM = String.raw`(function() {
|
|
106450
|
+
if (window.__HF_VIRTUAL_TIME__) return;
|
|
106451
|
+
|
|
106452
|
+
var virtualNowMs = 0;
|
|
106453
|
+
var rafId = 1;
|
|
106454
|
+
var rafQueue = [];
|
|
106455
|
+
var OriginalDate = Date;
|
|
106456
|
+
var originalSetTimeout = window.setTimeout.bind(window);
|
|
106457
|
+
var originalClearTimeout = window.clearTimeout.bind(window);
|
|
106458
|
+
var originalSetInterval = window.setInterval.bind(window);
|
|
106459
|
+
var originalClearInterval = window.clearInterval.bind(window);
|
|
106460
|
+
var originalRequestAnimationFrame = window.requestAnimationFrame
|
|
106461
|
+
? window.requestAnimationFrame.bind(window)
|
|
106462
|
+
: null;
|
|
106463
|
+
var originalCancelAnimationFrame = window.cancelAnimationFrame
|
|
106464
|
+
? window.cancelAnimationFrame.bind(window)
|
|
106465
|
+
: null;
|
|
106466
|
+
|
|
106467
|
+
function flushAnimationFrame() {
|
|
106468
|
+
if (!rafQueue.length) return;
|
|
106469
|
+
var current = rafQueue.slice();
|
|
106470
|
+
rafQueue.length = 0;
|
|
106471
|
+
for (var i = 0; i < current.length; i++) {
|
|
106472
|
+
var entry = current[i];
|
|
106473
|
+
if (entry.cancelled) continue;
|
|
106474
|
+
try {
|
|
106475
|
+
entry.callback(virtualNowMs);
|
|
106476
|
+
} catch {}
|
|
106477
|
+
}
|
|
106478
|
+
}
|
|
106479
|
+
|
|
106480
|
+
function VirtualDate() {
|
|
106481
|
+
var args = Array.prototype.slice.call(arguments);
|
|
106482
|
+
if (!(this instanceof VirtualDate)) {
|
|
106483
|
+
return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
|
|
106484
|
+
}
|
|
106485
|
+
var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
|
|
106486
|
+
Object.setPrototypeOf(instance, VirtualDate.prototype);
|
|
106487
|
+
return instance;
|
|
106488
|
+
}
|
|
106489
|
+
|
|
106490
|
+
VirtualDate.prototype = OriginalDate.prototype;
|
|
106491
|
+
Object.setPrototypeOf(VirtualDate, OriginalDate);
|
|
106492
|
+
VirtualDate.now = function() { return virtualNowMs; };
|
|
106493
|
+
VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
|
|
106494
|
+
VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
|
|
106495
|
+
|
|
106496
|
+
try {
|
|
106497
|
+
Object.defineProperty(window, "Date", {
|
|
106498
|
+
configurable: true,
|
|
106499
|
+
writable: true,
|
|
106500
|
+
value: VirtualDate,
|
|
106501
|
+
});
|
|
106502
|
+
} catch {}
|
|
106503
|
+
|
|
106504
|
+
if (window.performance && typeof window.performance.now === "function") {
|
|
106505
|
+
try {
|
|
106506
|
+
Object.defineProperty(window.performance, "now", {
|
|
106507
|
+
configurable: true,
|
|
106508
|
+
value: function() { return virtualNowMs; },
|
|
106509
|
+
});
|
|
106510
|
+
} catch {}
|
|
106511
|
+
}
|
|
106512
|
+
|
|
106513
|
+
window.requestAnimationFrame = function(callback) {
|
|
106514
|
+
if (typeof callback !== "function") return 0;
|
|
106515
|
+
var entry = { id: rafId++, callback: callback, cancelled: false };
|
|
106516
|
+
rafQueue.push(entry);
|
|
106517
|
+
return entry.id;
|
|
106518
|
+
};
|
|
106519
|
+
window.cancelAnimationFrame = function(id) {
|
|
106520
|
+
for (var i = 0; i < rafQueue.length; i++) {
|
|
106521
|
+
if (rafQueue[i].id === id) {
|
|
106522
|
+
rafQueue[i].cancelled = true;
|
|
106523
|
+
}
|
|
106524
|
+
}
|
|
106525
|
+
};
|
|
106526
|
+
|
|
106527
|
+
window.__HF_VIRTUAL_TIME__ = {
|
|
106528
|
+
originalSetTimeout: originalSetTimeout,
|
|
106529
|
+
originalClearTimeout: originalClearTimeout,
|
|
106530
|
+
originalSetInterval: originalSetInterval,
|
|
106531
|
+
originalClearInterval: originalClearInterval,
|
|
106532
|
+
originalRequestAnimationFrame: originalRequestAnimationFrame,
|
|
106533
|
+
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
106534
|
+
seekToTime: function(nextTimeMs) {
|
|
106535
|
+
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
106536
|
+
virtualNowMs = safeTimeMs;
|
|
106537
|
+
flushAnimationFrame();
|
|
106538
|
+
return virtualNowMs;
|
|
106539
|
+
},
|
|
106540
|
+
getTime: function() {
|
|
106541
|
+
return virtualNowMs;
|
|
106542
|
+
},
|
|
106543
|
+
};
|
|
106544
|
+
})();`;
|
|
105874
106545
|
var RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
|
|
105875
106546
|
var RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
|
|
105876
106547
|
var RENDER_SEEK_STEP = Math.max(
|
|
@@ -105882,6 +106553,10 @@ var RENDER_SEEK_OFFSET_FRACTION = Math.max(
|
|
|
105882
106553
|
Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
|
|
105883
106554
|
);
|
|
105884
106555
|
var RENDER_MODE_SCRIPT = `(function() {
|
|
106556
|
+
var __realSetTimeout =
|
|
106557
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
|
|
106558
|
+
? window.__HF_VIRTUAL_TIME__.originalSetTimeout
|
|
106559
|
+
: window.setTimeout.bind(window);
|
|
105885
106560
|
var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
|
|
105886
106561
|
var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
|
|
105887
106562
|
var __seekStep = ${RENDER_SEEK_STEP};
|
|
@@ -105975,40 +106650,88 @@ var RENDER_MODE_SCRIPT = `(function() {
|
|
|
105975
106650
|
window.__renderReady = true;
|
|
105976
106651
|
return;
|
|
105977
106652
|
}
|
|
105978
|
-
|
|
106653
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
105979
106654
|
return;
|
|
105980
106655
|
}
|
|
105981
106656
|
if (installMediaFallbackPlayer()) {
|
|
105982
106657
|
return;
|
|
105983
106658
|
}
|
|
105984
|
-
|
|
106659
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
105985
106660
|
}
|
|
105986
106661
|
waitForPlayer();
|
|
105987
106662
|
})();`;
|
|
106663
|
+
var HF_EARLY_STUB = `(function() {
|
|
106664
|
+
if (typeof window === "undefined") return;
|
|
106665
|
+
if (!window.__hf) window.__hf = {};
|
|
106666
|
+
})();`;
|
|
105988
106667
|
var HF_BRIDGE_SCRIPT = `(function() {
|
|
106668
|
+
var __realSetInterval =
|
|
106669
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
|
|
106670
|
+
? window.__HF_VIRTUAL_TIME__.originalSetInterval
|
|
106671
|
+
: window.setInterval.bind(window);
|
|
106672
|
+
var __realClearInterval =
|
|
106673
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
|
|
106674
|
+
? window.__HF_VIRTUAL_TIME__.originalClearInterval
|
|
106675
|
+
: window.clearInterval.bind(window);
|
|
105989
106676
|
function getDeclaredDuration() {
|
|
105990
106677
|
var root = document.querySelector('[data-composition-id]');
|
|
105991
106678
|
if (!root) return 0;
|
|
105992
106679
|
var d = Number(root.getAttribute('data-duration'));
|
|
105993
106680
|
return Number.isFinite(d) && d > 0 ? d : 0;
|
|
105994
106681
|
}
|
|
106682
|
+
function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
|
|
106683
|
+
var frames;
|
|
106684
|
+
try {
|
|
106685
|
+
frames = frameWindow.frames;
|
|
106686
|
+
} catch (_error) {
|
|
106687
|
+
return;
|
|
106688
|
+
}
|
|
106689
|
+
if (!frames || typeof frames.length !== "number") return;
|
|
106690
|
+
for (var i = 0; i < frames.length; i++) {
|
|
106691
|
+
var childWindow = null;
|
|
106692
|
+
try {
|
|
106693
|
+
childWindow = frames[i];
|
|
106694
|
+
if (!childWindow || childWindow === frameWindow) continue;
|
|
106695
|
+
if (
|
|
106696
|
+
childWindow.__HF_VIRTUAL_TIME__ &&
|
|
106697
|
+
typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
|
|
106698
|
+
) {
|
|
106699
|
+
childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
106700
|
+
}
|
|
106701
|
+
} catch (_error) {
|
|
106702
|
+
continue;
|
|
106703
|
+
}
|
|
106704
|
+
seekSameOriginChildFrames(childWindow, nextTimeMs);
|
|
106705
|
+
}
|
|
106706
|
+
}
|
|
105995
106707
|
function bridge() {
|
|
105996
106708
|
var p = window.__player;
|
|
105997
106709
|
if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
|
|
105998
106710
|
return false;
|
|
105999
106711
|
}
|
|
106000
|
-
window.__hf
|
|
106001
|
-
|
|
106712
|
+
var hf = window.__hf || {};
|
|
106713
|
+
Object.defineProperty(hf, "duration", {
|
|
106714
|
+
configurable: true,
|
|
106715
|
+
enumerable: true,
|
|
106716
|
+
get: function() {
|
|
106002
106717
|
var d = p.getDuration();
|
|
106003
106718
|
return d > 0 ? d : getDeclaredDuration();
|
|
106004
106719
|
},
|
|
106005
|
-
|
|
106720
|
+
});
|
|
106721
|
+
hf.seek = function(t) {
|
|
106722
|
+
p.renderSeek(t);
|
|
106723
|
+
var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
|
|
106724
|
+
if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
|
|
106725
|
+
window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
106726
|
+
}
|
|
106727
|
+
seekSameOriginChildFrames(window, nextTimeMs);
|
|
106006
106728
|
};
|
|
106729
|
+
window.__hf = hf;
|
|
106007
106730
|
return true;
|
|
106008
106731
|
}
|
|
106009
106732
|
if (bridge()) return;
|
|
106010
|
-
var iv =
|
|
106011
|
-
if (bridge())
|
|
106733
|
+
var iv = __realSetInterval(function() {
|
|
106734
|
+
if (bridge()) __realClearInterval(iv);
|
|
106012
106735
|
}, 50);
|
|
106013
106736
|
})();`;
|
|
106014
106737
|
function stripEmbeddedRuntimeScripts(html) {
|
|
@@ -106070,8 +106793,22 @@ function injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbedded) {
|
|
|
106070
106793
|
}
|
|
106071
106794
|
return html;
|
|
106072
106795
|
}
|
|
106796
|
+
function injectScriptsAtHeadStart(html, scripts) {
|
|
106797
|
+
if (scripts.length === 0) return html;
|
|
106798
|
+
const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
|
|
106799
|
+
if (html.includes("<head")) {
|
|
106800
|
+
return html.replace(/<head\b[^>]*>/i, (match2) => `${match2}
|
|
106801
|
+
${headTags}`);
|
|
106802
|
+
}
|
|
106803
|
+
if (html.includes("<body")) {
|
|
106804
|
+
return html.replace("<body", () => `${headTags}
|
|
106805
|
+
<body`);
|
|
106806
|
+
}
|
|
106807
|
+
return headTags + "\n" + html;
|
|
106808
|
+
}
|
|
106073
106809
|
function createFileServer2(options) {
|
|
106074
106810
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
106811
|
+
const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
|
|
106075
106812
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
106076
106813
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
106077
106814
|
const app = new Hono2();
|
|
@@ -106095,7 +106832,11 @@ function createFileServer2(options) {
|
|
|
106095
106832
|
if (ext === ".html") {
|
|
106096
106833
|
const rawHtml = readFileSync6(filePath, "utf-8");
|
|
106097
106834
|
const isIndex = relativePath === "index.html";
|
|
106098
|
-
|
|
106835
|
+
let html = rawHtml;
|
|
106836
|
+
if (preHeadScripts.length > 0) {
|
|
106837
|
+
html = injectScriptsAtHeadStart(html, preHeadScripts);
|
|
106838
|
+
}
|
|
106839
|
+
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
106099
106840
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
106100
106841
|
}
|
|
106101
106842
|
const content = readFileSync6(filePath);
|
|
@@ -106543,6 +107284,37 @@ function dedupeElementsById(elements) {
|
|
|
106543
107284
|
}
|
|
106544
107285
|
return Array.from(deduped.values());
|
|
106545
107286
|
}
|
|
107287
|
+
var INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
107288
|
+
function stripJsComments(source2) {
|
|
107289
|
+
return source2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
107290
|
+
}
|
|
107291
|
+
function detectRenderModeHints(html) {
|
|
107292
|
+
const reasons = [];
|
|
107293
|
+
const { document: document2 } = parseHTML(html);
|
|
107294
|
+
if (document2.querySelector("iframe")) {
|
|
107295
|
+
reasons.push({
|
|
107296
|
+
code: "iframe",
|
|
107297
|
+
message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
|
|
107298
|
+
});
|
|
107299
|
+
}
|
|
107300
|
+
let scriptMatch;
|
|
107301
|
+
const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
|
|
107302
|
+
while ((scriptMatch = scriptPattern.exec(html)) !== null) {
|
|
107303
|
+
const attrs = scriptMatch[1] || "";
|
|
107304
|
+
if (/\bsrc\s*=/i.test(attrs)) continue;
|
|
107305
|
+
const content = stripJsComments(scriptMatch[2] || "");
|
|
107306
|
+
if (!/requestAnimationFrame\s*\(/.test(content)) continue;
|
|
107307
|
+
reasons.push({
|
|
107308
|
+
code: "requestAnimationFrame",
|
|
107309
|
+
message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
|
|
107310
|
+
});
|
|
107311
|
+
break;
|
|
107312
|
+
}
|
|
107313
|
+
return {
|
|
107314
|
+
recommendScreenshot: reasons.length > 0,
|
|
107315
|
+
reasons
|
|
107316
|
+
};
|
|
107317
|
+
}
|
|
106546
107318
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
106547
107319
|
let filePath = src;
|
|
106548
107320
|
if (isHttpUrl(src)) {
|
|
@@ -107106,6 +107878,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
107106
107878
|
/(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
|
|
107107
107879
|
"$1"
|
|
107108
107880
|
);
|
|
107881
|
+
const renderModeHints = detectRenderModeHints(sanitizedHtml);
|
|
107109
107882
|
const coalescedHtml = await injectDeterministicFontFaces(
|
|
107110
107883
|
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
107111
107884
|
);
|
|
@@ -107149,7 +107922,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
107149
107922
|
externalAssets,
|
|
107150
107923
|
width,
|
|
107151
107924
|
height,
|
|
107152
|
-
staticDuration
|
|
107925
|
+
staticDuration,
|
|
107926
|
+
renderModeHints
|
|
107153
107927
|
};
|
|
107154
107928
|
}
|
|
107155
107929
|
async function discoverMediaFromBrowser(page) {
|
|
@@ -107243,7 +108017,8 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
|
|
|
107243
108017
|
subCompositions,
|
|
107244
108018
|
videos,
|
|
107245
108019
|
audios,
|
|
107246
|
-
unresolvedCompositions: remaining
|
|
108020
|
+
unresolvedCompositions: remaining,
|
|
108021
|
+
renderModeHints: compiled.renderModeHints
|
|
107247
108022
|
};
|
|
107248
108023
|
}
|
|
107249
108024
|
|
|
@@ -107293,6 +108068,24 @@ async function safeCleanup(label, fn, log = defaultLogger) {
|
|
|
107293
108068
|
});
|
|
107294
108069
|
}
|
|
107295
108070
|
}
|
|
108071
|
+
var frameDirMaxIndexCache = /* @__PURE__ */ new Map();
|
|
108072
|
+
var FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
|
|
108073
|
+
function getMaxFrameIndex(frameDir) {
|
|
108074
|
+
const cached = frameDirMaxIndexCache.get(frameDir);
|
|
108075
|
+
if (cached !== void 0) return cached;
|
|
108076
|
+
let max = 0;
|
|
108077
|
+
try {
|
|
108078
|
+
for (const name of readdirSync6(frameDir)) {
|
|
108079
|
+
const m = FRAME_FILENAME_RE.exec(name);
|
|
108080
|
+
if (!m) continue;
|
|
108081
|
+
const n = Number(m[1]);
|
|
108082
|
+
if (Number.isFinite(n) && n > max) max = n;
|
|
108083
|
+
}
|
|
108084
|
+
} catch {
|
|
108085
|
+
}
|
|
108086
|
+
frameDirMaxIndexCache.set(frameDir, max);
|
|
108087
|
+
return max;
|
|
108088
|
+
}
|
|
107296
108089
|
var RenderCancelledError = class extends Error {
|
|
107297
108090
|
reason;
|
|
107298
108091
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -107380,11 +108173,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
107380
108173
|
end: a.end,
|
|
107381
108174
|
mediaStart: a.mediaStart
|
|
107382
108175
|
})),
|
|
107383
|
-
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
108176
|
+
subCompositions: Array.from(compiled.subCompositions.keys()),
|
|
108177
|
+
renderModeHints: compiled.renderModeHints
|
|
107384
108178
|
};
|
|
107385
108179
|
writeFileSync4(join15(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
107386
108180
|
}
|
|
107387
108181
|
}
|
|
108182
|
+
function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
108183
|
+
if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
|
|
108184
|
+
cfg.forceScreenshot = true;
|
|
108185
|
+
log.warn("Auto-selected screenshot capture mode for render compatibility", {
|
|
108186
|
+
reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
|
|
108187
|
+
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
108188
|
+
});
|
|
108189
|
+
}
|
|
107388
108190
|
function createRenderJob(config2) {
|
|
107389
108191
|
return {
|
|
107390
108192
|
id: randomUUID(),
|
|
@@ -107497,6 +108299,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107497
108299
|
let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
|
|
107498
108300
|
assertNotAborted();
|
|
107499
108301
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
108302
|
+
applyRenderModeHints(cfg, compiled, log);
|
|
107500
108303
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
107501
108304
|
log.info("Compiled composition metadata", {
|
|
107502
108305
|
entryFile,
|
|
@@ -107504,7 +108307,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107504
108307
|
width: compiled.width,
|
|
107505
108308
|
height: compiled.height,
|
|
107506
108309
|
videoCount: compiled.videos.length,
|
|
107507
|
-
audioCount: compiled.audios.length
|
|
108310
|
+
audioCount: compiled.audios.length,
|
|
108311
|
+
renderModeHints: compiled.renderModeHints
|
|
107508
108312
|
});
|
|
107509
108313
|
const composition = {
|
|
107510
108314
|
duration: compiled.staticDuration,
|
|
@@ -107524,7 +108328,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107524
108328
|
fileServer = await createFileServer2({
|
|
107525
108329
|
projectDir,
|
|
107526
108330
|
compiledDir: join15(workDir, "compiled"),
|
|
107527
|
-
port: 0
|
|
108331
|
+
port: 0,
|
|
108332
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
107528
108333
|
});
|
|
107529
108334
|
assertNotAborted();
|
|
107530
108335
|
const captureOpts = {
|
|
@@ -107680,7 +108485,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107680
108485
|
}
|
|
107681
108486
|
}
|
|
107682
108487
|
}
|
|
107683
|
-
} catch {
|
|
108488
|
+
} catch (err) {
|
|
108489
|
+
log.warn("Failed to gather browser diagnostics for zero-duration composition", {
|
|
108490
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108491
|
+
});
|
|
107684
108492
|
diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
|
|
107685
108493
|
}
|
|
107686
108494
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
@@ -107704,8 +108512,26 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107704
108512
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
107705
108513
|
let frameLookup = null;
|
|
107706
108514
|
const compiledDir = join15(workDir, "compiled");
|
|
108515
|
+
let extractionResult = null;
|
|
108516
|
+
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
108517
|
+
if (composition.videos.length > 0) {
|
|
108518
|
+
await Promise.all(
|
|
108519
|
+
composition.videos.map(async (v) => {
|
|
108520
|
+
let videoPath = v.src;
|
|
108521
|
+
if (!videoPath.startsWith("/")) {
|
|
108522
|
+
const fromCompiled = existsSync15(join15(compiledDir, videoPath)) ? join15(compiledDir, videoPath) : join15(projectDir, videoPath);
|
|
108523
|
+
videoPath = fromCompiled;
|
|
108524
|
+
}
|
|
108525
|
+
if (!existsSync15(videoPath)) return;
|
|
108526
|
+
const meta = await extractVideoMetadata(videoPath);
|
|
108527
|
+
if (isHdrColorSpace(meta.colorSpace)) {
|
|
108528
|
+
nativeHdrVideoIds.add(v.id);
|
|
108529
|
+
}
|
|
108530
|
+
})
|
|
108531
|
+
);
|
|
108532
|
+
}
|
|
107707
108533
|
if (composition.videos.length > 0) {
|
|
107708
|
-
|
|
108534
|
+
extractionResult = await extractAllVideoFrames(
|
|
107709
108535
|
composition.videos,
|
|
107710
108536
|
projectDir,
|
|
107711
108537
|
{ fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
|
|
@@ -107740,6 +108566,23 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107740
108566
|
} else {
|
|
107741
108567
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
107742
108568
|
}
|
|
108569
|
+
let effectiveHdr;
|
|
108570
|
+
if (frameLookup) {
|
|
108571
|
+
const colorSpaces = (extractionResult?.extracted ?? []).map((ext) => ext.metadata.colorSpace);
|
|
108572
|
+
const info = analyzeCompositionHdr(colorSpaces);
|
|
108573
|
+
if (info.hasHdr && info.dominantTransfer) {
|
|
108574
|
+
effectiveHdr = { transfer: info.dominantTransfer };
|
|
108575
|
+
}
|
|
108576
|
+
}
|
|
108577
|
+
if (effectiveHdr && outputFormat !== "mp4") {
|
|
108578
|
+
log.info(`[Render] HDR source detected but format is ${outputFormat} \u2014 using SDR`);
|
|
108579
|
+
effectiveHdr = void 0;
|
|
108580
|
+
}
|
|
108581
|
+
if (effectiveHdr) {
|
|
108582
|
+
log.info(
|
|
108583
|
+
`[Render] HDR source detected \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
|
|
108584
|
+
);
|
|
108585
|
+
}
|
|
107743
108586
|
const stage3Start = Date.now();
|
|
107744
108587
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
107745
108588
|
const audioOutputPath = join15(workDir, "audio.aac");
|
|
@@ -107767,7 +108610,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107767
108610
|
fileServer = await createFileServer2({
|
|
107768
108611
|
projectDir,
|
|
107769
108612
|
compiledDir: join15(workDir, "compiled"),
|
|
107770
|
-
port: 0
|
|
108613
|
+
port: 0,
|
|
108614
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
107771
108615
|
});
|
|
107772
108616
|
assertNotAborted();
|
|
107773
108617
|
}
|
|
@@ -107784,218 +108628,398 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107784
108628
|
const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
107785
108629
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
107786
108630
|
const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
|
|
107787
|
-
const
|
|
107788
|
-
const
|
|
107789
|
-
const
|
|
107790
|
-
const baseEncoderOpts = {
|
|
107791
|
-
fps: job.config.fps,
|
|
107792
|
-
width,
|
|
107793
|
-
height,
|
|
107794
|
-
codec: preset.codec,
|
|
107795
|
-
preset: preset.preset,
|
|
107796
|
-
quality: effectiveQuality,
|
|
107797
|
-
bitrate: effectiveBitrate,
|
|
107798
|
-
pixelFormat: preset.pixelFormat,
|
|
107799
|
-
useGpu: job.config.useGpu
|
|
107800
|
-
};
|
|
108631
|
+
const hasHdrVideo = effectiveHdr && composition.videos.length > 0 && frameLookup;
|
|
108632
|
+
const encoderHdr = hasHdrVideo ? effectiveHdr : void 0;
|
|
108633
|
+
const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
|
|
107801
108634
|
job.framesRendered = 0;
|
|
107802
|
-
|
|
107803
|
-
|
|
107804
|
-
|
|
108635
|
+
if (hasHdrVideo) {
|
|
108636
|
+
log.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
|
|
108637
|
+
const hdrVideoIds = composition.videos.filter((v) => nativeHdrVideoIds.has(v.id)).map((v) => v.id);
|
|
108638
|
+
const hdrVideoSrcPaths = /* @__PURE__ */ new Map();
|
|
108639
|
+
for (const v of composition.videos) {
|
|
108640
|
+
if (!hdrVideoIds.includes(v.id)) continue;
|
|
108641
|
+
let srcPath = v.src;
|
|
108642
|
+
if (!srcPath.startsWith("/")) {
|
|
108643
|
+
const fromCompiled = join15(compiledDir, srcPath);
|
|
108644
|
+
srcPath = existsSync15(fromCompiled) ? fromCompiled : join15(projectDir, srcPath);
|
|
108645
|
+
}
|
|
108646
|
+
hdrVideoSrcPaths.set(v.id, srcPath);
|
|
108647
|
+
}
|
|
108648
|
+
const domSession = await createCaptureSession(
|
|
108649
|
+
fileServer.url,
|
|
108650
|
+
framesDir,
|
|
108651
|
+
captureOptions,
|
|
108652
|
+
createVideoFrameInjector(frameLookup),
|
|
108653
|
+
cfg
|
|
108654
|
+
);
|
|
108655
|
+
await initializeSession(domSession);
|
|
108656
|
+
assertNotAborted();
|
|
108657
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
108658
|
+
await initTransparentBackground(domSession.page);
|
|
108659
|
+
const hdrEncoder = await spawnStreamingEncoder(
|
|
107805
108660
|
videoOnlyPath,
|
|
107806
108661
|
{
|
|
107807
|
-
|
|
107808
|
-
|
|
108662
|
+
fps: job.config.fps,
|
|
108663
|
+
width,
|
|
108664
|
+
height,
|
|
108665
|
+
codec: preset.codec,
|
|
108666
|
+
preset: preset.preset,
|
|
108667
|
+
quality: preset.quality,
|
|
108668
|
+
pixelFormat: preset.pixelFormat,
|
|
108669
|
+
hdr: preset.hdr,
|
|
108670
|
+
rawInputFormat: "rgb48le"
|
|
107809
108671
|
},
|
|
107810
|
-
abortSignal
|
|
108672
|
+
abortSignal,
|
|
108673
|
+
{ ffmpegStreamingTimeout: 36e5 }
|
|
107811
108674
|
);
|
|
107812
108675
|
assertNotAborted();
|
|
107813
|
-
|
|
107814
|
-
|
|
107815
|
-
const
|
|
107816
|
-
|
|
107817
|
-
|
|
107818
|
-
const
|
|
107819
|
-
|
|
107820
|
-
|
|
107821
|
-
|
|
107822
|
-
|
|
107823
|
-
|
|
107824
|
-
|
|
107825
|
-
|
|
107826
|
-
|
|
107827
|
-
|
|
107828
|
-
|
|
107829
|
-
|
|
107830
|
-
|
|
107831
|
-
|
|
107832
|
-
|
|
107833
|
-
|
|
107834
|
-
|
|
107835
|
-
|
|
107836
|
-
|
|
107837
|
-
|
|
107838
|
-
|
|
107839
|
-
|
|
107840
|
-
|
|
107841
|
-
|
|
108676
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
108677
|
+
const hdrFrameDirs = /* @__PURE__ */ new Map();
|
|
108678
|
+
for (const [videoId, srcPath] of hdrVideoSrcPaths) {
|
|
108679
|
+
const video = composition.videos.find((v) => v.id === videoId);
|
|
108680
|
+
if (!video) continue;
|
|
108681
|
+
const frameDir = join15(framesDir, `hdr_${videoId}`);
|
|
108682
|
+
mkdirSync10(frameDir, { recursive: true });
|
|
108683
|
+
const duration = video.end - video.start;
|
|
108684
|
+
try {
|
|
108685
|
+
execSync2(
|
|
108686
|
+
`ffmpeg -ss ${video.mediaStart} -i "${srcPath}" -t ${duration} -r ${job.config.fps} -vf "scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}" -pix_fmt rgb48le -c:v png "${join15(frameDir, "frame_%04d.png")}"`,
|
|
108687
|
+
{ maxBuffer: 1024 * 1024, stdio: ["pipe", "pipe", "pipe"] }
|
|
108688
|
+
);
|
|
108689
|
+
} catch (err) {
|
|
108690
|
+
log.warn("HDR frame pre-extraction failed; loop will fill with black", {
|
|
108691
|
+
videoId,
|
|
108692
|
+
srcPath,
|
|
108693
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108694
|
+
});
|
|
108695
|
+
}
|
|
108696
|
+
hdrFrameDirs.set(videoId, frameDir);
|
|
108697
|
+
}
|
|
108698
|
+
assertNotAborted();
|
|
108699
|
+
try {
|
|
108700
|
+
const beforeCaptureHook = domSession.onBeforeCapture;
|
|
108701
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
108702
|
+
assertNotAborted();
|
|
108703
|
+
const time = i / job.config.fps;
|
|
108704
|
+
await domSession.page.evaluate((t) => {
|
|
108705
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
|
|
108706
|
+
}, time);
|
|
108707
|
+
if (beforeCaptureHook) {
|
|
108708
|
+
await beforeCaptureHook(domSession.page, time);
|
|
108709
|
+
}
|
|
108710
|
+
const stackingInfo = await queryElementStacking(domSession.page, nativeHdrVideoIds);
|
|
108711
|
+
const layers = groupIntoLayers(stackingInfo);
|
|
108712
|
+
if (i % 30 === 0) {
|
|
108713
|
+
const hdrEl = stackingInfo.find((e) => e.isHdr);
|
|
108714
|
+
const hdrInLayers = layers.some((l) => l.type === "hdr");
|
|
108715
|
+
log.debug("[Render] HDR layer composite frame", {
|
|
108716
|
+
frame: i,
|
|
108717
|
+
time: time.toFixed(2),
|
|
108718
|
+
hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
|
|
108719
|
+
hdrLayerPresent: hdrInLayers,
|
|
108720
|
+
layerCount: layers.length
|
|
108721
|
+
});
|
|
108722
|
+
}
|
|
108723
|
+
const canvas = Buffer.alloc(width * height * 6);
|
|
108724
|
+
for (const layer of layers) {
|
|
108725
|
+
if (layer.type === "hdr") {
|
|
108726
|
+
const el = layer.element;
|
|
108727
|
+
const frameDir = hdrFrameDirs.get(el.id);
|
|
108728
|
+
const video = composition.videos.find((v) => v.id === el.id);
|
|
108729
|
+
if (!frameDir || !video) continue;
|
|
108730
|
+
const videoFrameIndex = Math.round((time - video.start) * job.config.fps) + 1;
|
|
108731
|
+
const maxIndex = getMaxFrameIndex(frameDir);
|
|
108732
|
+
const inBounds = videoFrameIndex >= 1 && (maxIndex === 0 || videoFrameIndex <= maxIndex);
|
|
108733
|
+
const framePath = inBounds ? join15(frameDir, `frame_${String(videoFrameIndex).padStart(4, "0")}.png`) : null;
|
|
108734
|
+
if (framePath !== null && existsSync15(framePath)) {
|
|
108735
|
+
try {
|
|
108736
|
+
const hdrRgb = decodePngToRgb48le(readFileSync9(framePath)).data;
|
|
108737
|
+
blitRgb48leRegion(
|
|
108738
|
+
canvas,
|
|
108739
|
+
hdrRgb,
|
|
108740
|
+
el.x,
|
|
108741
|
+
el.y,
|
|
108742
|
+
el.width,
|
|
108743
|
+
el.height,
|
|
108744
|
+
width,
|
|
108745
|
+
height,
|
|
108746
|
+
el.opacity < 0.999 ? el.opacity : void 0
|
|
108747
|
+
);
|
|
108748
|
+
} catch (err) {
|
|
108749
|
+
log.warn("HDR layer decode/blit failed; skipping layer for frame", {
|
|
108750
|
+
frameIndex: i,
|
|
108751
|
+
videoId: el.id,
|
|
108752
|
+
framePath,
|
|
108753
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108754
|
+
});
|
|
108755
|
+
}
|
|
108756
|
+
}
|
|
108757
|
+
} else {
|
|
108758
|
+
const allElementIds = stackingInfo.map((e) => e.id);
|
|
108759
|
+
const layerIds = new Set(layer.elementIds);
|
|
108760
|
+
const hideIds = allElementIds.filter(
|
|
108761
|
+
(id) => !layerIds.has(id) || nativeHdrVideoIds.has(id)
|
|
107842
108762
|
);
|
|
108763
|
+
await hideVideoElements(domSession.page, hideIds);
|
|
108764
|
+
const domPng = await captureAlphaPng(domSession.page, width, height);
|
|
108765
|
+
await showVideoElements(domSession.page, hideIds);
|
|
108766
|
+
await domSession.page.evaluate((t) => {
|
|
108767
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
|
|
108768
|
+
}, time);
|
|
108769
|
+
try {
|
|
108770
|
+
const { data: domRgba } = decodePng(domPng);
|
|
108771
|
+
const hdrTransfer = effectiveHdr ? effectiveHdr.transfer : "hlg";
|
|
108772
|
+
blitRgba8OverRgb48le(domRgba, canvas, width, height, hdrTransfer);
|
|
108773
|
+
} catch (err) {
|
|
108774
|
+
log.warn("DOM layer decode/blit failed; skipping overlay for frame", {
|
|
108775
|
+
frameIndex: i,
|
|
108776
|
+
layerIds: layer.elementIds,
|
|
108777
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108778
|
+
});
|
|
108779
|
+
}
|
|
107843
108780
|
}
|
|
107844
|
-
},
|
|
107845
|
-
onFrameBuffer,
|
|
107846
|
-
cfg
|
|
107847
|
-
);
|
|
107848
|
-
if (probeSession) {
|
|
107849
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
107850
|
-
await closeCaptureSession(probeSession);
|
|
107851
|
-
probeSession = null;
|
|
107852
|
-
}
|
|
107853
|
-
} else {
|
|
107854
|
-
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
107855
|
-
const session = probeSession ?? await createCaptureSession(
|
|
107856
|
-
fileServer.url,
|
|
107857
|
-
framesDir,
|
|
107858
|
-
captureOptions,
|
|
107859
|
-
videoInjector,
|
|
107860
|
-
cfg
|
|
107861
|
-
);
|
|
107862
|
-
if (probeSession) {
|
|
107863
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
107864
|
-
probeSession = null;
|
|
107865
|
-
}
|
|
107866
|
-
try {
|
|
107867
|
-
if (!session.isInitialized) {
|
|
107868
|
-
await initializeSession(session);
|
|
107869
108781
|
}
|
|
107870
|
-
|
|
107871
|
-
|
|
107872
|
-
|
|
107873
|
-
assertNotAborted();
|
|
107874
|
-
const time = i / job.config.fps;
|
|
107875
|
-
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
107876
|
-
await reorderBuffer.waitForFrame(i);
|
|
107877
|
-
currentEncoder.writeFrame(buffer);
|
|
107878
|
-
reorderBuffer.advanceTo(i + 1);
|
|
107879
|
-
job.framesRendered = i + 1;
|
|
108782
|
+
hdrEncoder.writeFrame(canvas);
|
|
108783
|
+
job.framesRendered = i + 1;
|
|
108784
|
+
if ((i + 1) % 10 === 0 || i + 1 === job.totalFrames) {
|
|
107880
108785
|
const frameProgress = (i + 1) / job.totalFrames;
|
|
107881
|
-
const progress = 25 + frameProgress * 55;
|
|
107882
108786
|
updateJobStatus(
|
|
107883
108787
|
job,
|
|
107884
108788
|
"rendering",
|
|
107885
|
-
`
|
|
107886
|
-
Math.round(
|
|
108789
|
+
`HDR composite frame ${i + 1}/${job.totalFrames}`,
|
|
108790
|
+
Math.round(25 + frameProgress * 55),
|
|
107887
108791
|
onProgress
|
|
107888
108792
|
);
|
|
107889
108793
|
}
|
|
107890
|
-
} finally {
|
|
107891
|
-
lastBrowserConsole = session.browserConsoleBuffer;
|
|
107892
|
-
await closeCaptureSession(session);
|
|
107893
108794
|
}
|
|
108795
|
+
} finally {
|
|
108796
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
108797
|
+
await closeCaptureSession(domSession);
|
|
107894
108798
|
}
|
|
107895
|
-
const
|
|
108799
|
+
const hdrEncodeResult = await hdrEncoder.close();
|
|
107896
108800
|
assertNotAborted();
|
|
107897
|
-
if (!
|
|
107898
|
-
throw new Error(`
|
|
108801
|
+
if (!hdrEncodeResult.success) {
|
|
108802
|
+
throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
|
|
107899
108803
|
}
|
|
107900
108804
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
107901
|
-
perfStages.encodeMs =
|
|
108805
|
+
perfStages.encodeMs = hdrEncodeResult.durationMs;
|
|
107902
108806
|
} else {
|
|
107903
|
-
|
|
107904
|
-
|
|
107905
|
-
await
|
|
107906
|
-
|
|
107907
|
-
|
|
107908
|
-
|
|
107909
|
-
|
|
107910
|
-
|
|
107911
|
-
|
|
107912
|
-
|
|
107913
|
-
|
|
107914
|
-
|
|
107915
|
-
|
|
107916
|
-
|
|
108807
|
+
let streamingEncoder = null;
|
|
108808
|
+
if (enableStreamingEncode) {
|
|
108809
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
108810
|
+
videoOnlyPath,
|
|
108811
|
+
{
|
|
108812
|
+
fps: job.config.fps,
|
|
108813
|
+
width,
|
|
108814
|
+
height,
|
|
108815
|
+
codec: preset.codec,
|
|
108816
|
+
preset: preset.preset,
|
|
108817
|
+
quality: preset.quality,
|
|
108818
|
+
pixelFormat: preset.pixelFormat,
|
|
108819
|
+
useGpu: job.config.useGpu,
|
|
108820
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
108821
|
+
hdr: preset.hdr
|
|
108822
|
+
},
|
|
108823
|
+
abortSignal
|
|
108824
|
+
);
|
|
108825
|
+
assertNotAborted();
|
|
108826
|
+
}
|
|
108827
|
+
if (enableStreamingEncode && streamingEncoder) {
|
|
108828
|
+
const reorderBuffer = createFrameReorderBuffer(0, job.totalFrames);
|
|
108829
|
+
const currentEncoder = streamingEncoder;
|
|
108830
|
+
if (workerCount > 1) {
|
|
108831
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
108832
|
+
const onFrameBuffer = async (frameIndex, buffer) => {
|
|
108833
|
+
await reorderBuffer.waitForFrame(frameIndex);
|
|
108834
|
+
currentEncoder.writeFrame(buffer);
|
|
108835
|
+
reorderBuffer.advanceTo(frameIndex + 1);
|
|
108836
|
+
};
|
|
108837
|
+
await executeParallelCapture(
|
|
108838
|
+
fileServer.url,
|
|
108839
|
+
workDir,
|
|
108840
|
+
tasks,
|
|
108841
|
+
captureOptions,
|
|
108842
|
+
() => createVideoFrameInjector(frameLookup),
|
|
108843
|
+
abortSignal,
|
|
108844
|
+
(progress) => {
|
|
108845
|
+
job.framesRendered = progress.capturedFrames;
|
|
108846
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
108847
|
+
const progressPct = 25 + frameProgress * 55;
|
|
108848
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
108849
|
+
updateJobStatus(
|
|
108850
|
+
job,
|
|
108851
|
+
"rendering",
|
|
108852
|
+
`Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
108853
|
+
Math.round(progressPct),
|
|
108854
|
+
onProgress
|
|
108855
|
+
);
|
|
108856
|
+
}
|
|
108857
|
+
},
|
|
108858
|
+
onFrameBuffer,
|
|
108859
|
+
cfg
|
|
108860
|
+
);
|
|
108861
|
+
if (probeSession) {
|
|
108862
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108863
|
+
await closeCaptureSession(probeSession);
|
|
108864
|
+
probeSession = null;
|
|
108865
|
+
}
|
|
108866
|
+
} else {
|
|
108867
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
108868
|
+
const session = probeSession ?? await createCaptureSession(
|
|
108869
|
+
fileServer.url,
|
|
108870
|
+
framesDir,
|
|
108871
|
+
captureOptions,
|
|
108872
|
+
videoInjector,
|
|
108873
|
+
cfg
|
|
108874
|
+
);
|
|
108875
|
+
if (probeSession) {
|
|
108876
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108877
|
+
probeSession = null;
|
|
108878
|
+
}
|
|
108879
|
+
try {
|
|
108880
|
+
if (!session.isInitialized) {
|
|
108881
|
+
await initializeSession(session);
|
|
108882
|
+
}
|
|
108883
|
+
assertNotAborted();
|
|
108884
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108885
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
108886
|
+
assertNotAborted();
|
|
108887
|
+
const time = i / job.config.fps;
|
|
108888
|
+
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
108889
|
+
await reorderBuffer.waitForFrame(i);
|
|
108890
|
+
currentEncoder.writeFrame(buffer);
|
|
108891
|
+
reorderBuffer.advanceTo(i + 1);
|
|
108892
|
+
job.framesRendered = i + 1;
|
|
108893
|
+
const frameProgress = (i + 1) / job.totalFrames;
|
|
108894
|
+
const progress = 25 + frameProgress * 55;
|
|
107917
108895
|
updateJobStatus(
|
|
107918
108896
|
job,
|
|
107919
108897
|
"rendering",
|
|
107920
|
-
`
|
|
107921
|
-
Math.round(
|
|
108898
|
+
`Streaming frame ${i + 1}/${job.totalFrames}`,
|
|
108899
|
+
Math.round(progress),
|
|
107922
108900
|
onProgress
|
|
107923
108901
|
);
|
|
107924
108902
|
}
|
|
107925
|
-
}
|
|
107926
|
-
|
|
107927
|
-
|
|
107928
|
-
|
|
107929
|
-
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
107930
|
-
if (probeSession) {
|
|
107931
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
107932
|
-
await closeCaptureSession(probeSession);
|
|
107933
|
-
probeSession = null;
|
|
108903
|
+
} finally {
|
|
108904
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108905
|
+
await closeCaptureSession(session);
|
|
108906
|
+
}
|
|
107934
108907
|
}
|
|
107935
|
-
|
|
107936
|
-
|
|
107937
|
-
|
|
107938
|
-
|
|
107939
|
-
framesDir,
|
|
107940
|
-
captureOptions,
|
|
107941
|
-
videoInjector,
|
|
107942
|
-
cfg
|
|
107943
|
-
);
|
|
107944
|
-
if (probeSession) {
|
|
107945
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
107946
|
-
probeSession = null;
|
|
108908
|
+
const encodeResult = await currentEncoder.close();
|
|
108909
|
+
assertNotAborted();
|
|
108910
|
+
if (!encodeResult.success) {
|
|
108911
|
+
throw new Error(`Streaming encode failed: ${encodeResult.error}`);
|
|
107947
108912
|
}
|
|
107948
|
-
|
|
107949
|
-
|
|
107950
|
-
|
|
108913
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
108914
|
+
perfStages.encodeMs = encodeResult.durationMs;
|
|
108915
|
+
} else {
|
|
108916
|
+
if (workerCount > 1) {
|
|
108917
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
108918
|
+
await executeParallelCapture(
|
|
108919
|
+
fileServer.url,
|
|
108920
|
+
workDir,
|
|
108921
|
+
tasks,
|
|
108922
|
+
captureOptions,
|
|
108923
|
+
() => createVideoFrameInjector(frameLookup),
|
|
108924
|
+
abortSignal,
|
|
108925
|
+
(progress) => {
|
|
108926
|
+
job.framesRendered = progress.capturedFrames;
|
|
108927
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
108928
|
+
const progressPct = 25 + frameProgress * 45;
|
|
108929
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
108930
|
+
updateJobStatus(
|
|
108931
|
+
job,
|
|
108932
|
+
"rendering",
|
|
108933
|
+
`Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
108934
|
+
Math.round(progressPct),
|
|
108935
|
+
onProgress
|
|
108936
|
+
);
|
|
108937
|
+
}
|
|
108938
|
+
},
|
|
108939
|
+
void 0,
|
|
108940
|
+
cfg
|
|
108941
|
+
);
|
|
108942
|
+
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
108943
|
+
if (probeSession) {
|
|
108944
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108945
|
+
await closeCaptureSession(probeSession);
|
|
108946
|
+
probeSession = null;
|
|
107951
108947
|
}
|
|
107952
|
-
|
|
107953
|
-
|
|
107954
|
-
|
|
107955
|
-
|
|
107956
|
-
|
|
107957
|
-
|
|
107958
|
-
|
|
107959
|
-
|
|
107960
|
-
|
|
107961
|
-
|
|
107962
|
-
|
|
107963
|
-
|
|
107964
|
-
`Capturing frame ${i + 1}/${job.totalFrames}`,
|
|
107965
|
-
Math.round(progress),
|
|
107966
|
-
onProgress
|
|
107967
|
-
);
|
|
108948
|
+
} else {
|
|
108949
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
108950
|
+
const session = probeSession ?? await createCaptureSession(
|
|
108951
|
+
fileServer.url,
|
|
108952
|
+
framesDir,
|
|
108953
|
+
captureOptions,
|
|
108954
|
+
videoInjector,
|
|
108955
|
+
cfg
|
|
108956
|
+
);
|
|
108957
|
+
if (probeSession) {
|
|
108958
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108959
|
+
probeSession = null;
|
|
107968
108960
|
}
|
|
107969
|
-
|
|
107970
|
-
|
|
107971
|
-
|
|
108961
|
+
try {
|
|
108962
|
+
if (!session.isInitialized) {
|
|
108963
|
+
await initializeSession(session);
|
|
108964
|
+
}
|
|
108965
|
+
assertNotAborted();
|
|
108966
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108967
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
108968
|
+
assertNotAborted();
|
|
108969
|
+
const time = i / job.config.fps;
|
|
108970
|
+
await captureFrame(session, i, time);
|
|
108971
|
+
job.framesRendered = i + 1;
|
|
108972
|
+
const frameProgress = (i + 1) / job.totalFrames;
|
|
108973
|
+
const progress = 25 + frameProgress * 45;
|
|
108974
|
+
updateJobStatus(
|
|
108975
|
+
job,
|
|
108976
|
+
"rendering",
|
|
108977
|
+
`Capturing frame ${i + 1}/${job.totalFrames}`,
|
|
108978
|
+
Math.round(progress),
|
|
108979
|
+
onProgress
|
|
108980
|
+
);
|
|
108981
|
+
}
|
|
108982
|
+
} finally {
|
|
108983
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108984
|
+
await closeCaptureSession(session);
|
|
108985
|
+
}
|
|
108986
|
+
}
|
|
108987
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
108988
|
+
const stage5Start = Date.now();
|
|
108989
|
+
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
108990
|
+
const frameExt = needsAlpha ? "png" : "jpg";
|
|
108991
|
+
const framePattern = `frame_%06d.${frameExt}`;
|
|
108992
|
+
const encoderOpts = {
|
|
108993
|
+
fps: job.config.fps,
|
|
108994
|
+
width,
|
|
108995
|
+
height,
|
|
108996
|
+
codec: preset.codec,
|
|
108997
|
+
preset: preset.preset,
|
|
108998
|
+
quality: preset.quality,
|
|
108999
|
+
pixelFormat: preset.pixelFormat,
|
|
109000
|
+
useGpu: job.config.useGpu,
|
|
109001
|
+
hdr: preset.hdr
|
|
109002
|
+
};
|
|
109003
|
+
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
109004
|
+
framesDir,
|
|
109005
|
+
framePattern,
|
|
109006
|
+
videoOnlyPath,
|
|
109007
|
+
encoderOpts,
|
|
109008
|
+
chunkedEncodeSize,
|
|
109009
|
+
abortSignal
|
|
109010
|
+
) : await encodeFramesFromDir(
|
|
109011
|
+
framesDir,
|
|
109012
|
+
framePattern,
|
|
109013
|
+
videoOnlyPath,
|
|
109014
|
+
encoderOpts,
|
|
109015
|
+
abortSignal
|
|
109016
|
+
);
|
|
109017
|
+
assertNotAborted();
|
|
109018
|
+
if (!encodeResult.success) {
|
|
109019
|
+
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
107972
109020
|
}
|
|
109021
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
107973
109022
|
}
|
|
107974
|
-
perfStages.captureMs = Date.now() - stage4Start;
|
|
107975
|
-
const stage5Start = Date.now();
|
|
107976
|
-
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
107977
|
-
const frameExt = needsAlpha ? "png" : "jpg";
|
|
107978
|
-
const framePattern = `frame_%06d.${frameExt}`;
|
|
107979
|
-
const encoderOpts = baseEncoderOpts;
|
|
107980
|
-
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
107981
|
-
framesDir,
|
|
107982
|
-
framePattern,
|
|
107983
|
-
videoOnlyPath,
|
|
107984
|
-
encoderOpts,
|
|
107985
|
-
chunkedEncodeSize,
|
|
107986
|
-
abortSignal
|
|
107987
|
-
) : await encodeFramesFromDir(
|
|
107988
|
-
framesDir,
|
|
107989
|
-
framePattern,
|
|
107990
|
-
videoOnlyPath,
|
|
107991
|
-
encoderOpts,
|
|
107992
|
-
abortSignal
|
|
107993
|
-
);
|
|
107994
|
-
assertNotAborted();
|
|
107995
|
-
if (!encodeResult.success) {
|
|
107996
|
-
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
107997
|
-
}
|
|
107998
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
107999
109023
|
}
|
|
108000
109024
|
if (probeSession !== null) {
|
|
108001
109025
|
const remainingProbeSession = probeSession;
|