@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/public-server.js
CHANGED
|
@@ -92210,6 +92210,7 @@ import {
|
|
|
92210
92210
|
mkdirSync as mkdirSync10,
|
|
92211
92211
|
rmSync as rmSync3,
|
|
92212
92212
|
readFileSync as readFileSync9,
|
|
92213
|
+
readdirSync as readdirSync6,
|
|
92213
92214
|
writeFileSync as writeFileSync4,
|
|
92214
92215
|
copyFileSync as copyFileSync2,
|
|
92215
92216
|
appendFileSync
|
|
@@ -101457,6 +101458,8 @@ var DEFAULT_CONFIG = {
|
|
|
101457
101458
|
ffmpegEncodeTimeout: 6e5,
|
|
101458
101459
|
ffmpegProcessTimeout: 3e5,
|
|
101459
101460
|
ffmpegStreamingTimeout: 6e5,
|
|
101461
|
+
hdr: false,
|
|
101462
|
+
hdrAutoDetect: true,
|
|
101460
101463
|
audioGain: 1.35,
|
|
101461
101464
|
frameDataUriCacheLimit: 256,
|
|
101462
101465
|
playerReadyTimeout: 45e3,
|
|
@@ -101513,6 +101516,12 @@ function resolveConfig(overrides) {
|
|
|
101513
101516
|
"FFMPEG_STREAMING_TIMEOUT_MS",
|
|
101514
101517
|
DEFAULT_CONFIG.ffmpegStreamingTimeout
|
|
101515
101518
|
),
|
|
101519
|
+
hdr: (() => {
|
|
101520
|
+
const raw2 = env2("PRODUCER_HDR_TRANSFER");
|
|
101521
|
+
if (raw2 === "hlg" || raw2 === "pq") return { transfer: raw2 };
|
|
101522
|
+
return void 0;
|
|
101523
|
+
})(),
|
|
101524
|
+
hdrAutoDetect: envBool("PRODUCER_HDR_AUTO_DETECT", DEFAULT_CONFIG.hdrAutoDetect),
|
|
101516
101525
|
audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG.audioGain),
|
|
101517
101526
|
frameDataUriCacheLimit: Math.max(
|
|
101518
101527
|
32,
|
|
@@ -101709,7 +101718,8 @@ function buildChromeArgs(options, config2) {
|
|
|
101709
101718
|
"--font-render-hinting=none",
|
|
101710
101719
|
"--force-color-profile=srgb",
|
|
101711
101720
|
`--window-size=${options.width},${options.height}`,
|
|
101712
|
-
//
|
|
101721
|
+
// Prevent Chrome from throttling background tabs/timers — critical when the
|
|
101722
|
+
// page is offscreen during headless capture
|
|
101713
101723
|
"--disable-background-timer-throttling",
|
|
101714
101724
|
"--disable-backgrounding-occluded-windows",
|
|
101715
101725
|
"--disable-renderer-backgrounding",
|
|
@@ -103696,6 +103706,24 @@ async function pageScreenshotCapture(page, options) {
|
|
|
103696
103706
|
});
|
|
103697
103707
|
return Buffer.from(result.data, "base64");
|
|
103698
103708
|
}
|
|
103709
|
+
async function initTransparentBackground(page) {
|
|
103710
|
+
const client = await getCdpSession(page);
|
|
103711
|
+
await client.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
103712
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
103713
|
+
});
|
|
103714
|
+
}
|
|
103715
|
+
async function captureAlphaPng(page, width, height) {
|
|
103716
|
+
const client = await getCdpSession(page);
|
|
103717
|
+
const result = await client.send("Page.captureScreenshot", {
|
|
103718
|
+
format: "png",
|
|
103719
|
+
fromSurface: true,
|
|
103720
|
+
captureBeyondViewport: false,
|
|
103721
|
+
optimizeForSpeed: false,
|
|
103722
|
+
// must be false to preserve alpha
|
|
103723
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
103724
|
+
});
|
|
103725
|
+
return Buffer.from(result.data, "base64");
|
|
103726
|
+
}
|
|
103699
103727
|
async function injectVideoFramesBatch(page, updates) {
|
|
103700
103728
|
if (updates.length === 0) return;
|
|
103701
103729
|
await page.evaluate(
|
|
@@ -103717,16 +103745,7 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
103717
103745
|
video.parentNode?.insertBefore(img, video.nextSibling);
|
|
103718
103746
|
}
|
|
103719
103747
|
if (!img) continue;
|
|
103720
|
-
|
|
103721
|
-
img.style.position = computedStyle.position;
|
|
103722
|
-
img.style.width = computedStyle.width;
|
|
103723
|
-
img.style.height = computedStyle.height;
|
|
103724
|
-
img.style.top = computedStyle.top;
|
|
103725
|
-
img.style.left = computedStyle.left;
|
|
103726
|
-
img.style.right = computedStyle.right;
|
|
103727
|
-
img.style.bottom = computedStyle.bottom;
|
|
103728
|
-
img.style.inset = computedStyle.inset;
|
|
103729
|
-
} else {
|
|
103748
|
+
{
|
|
103730
103749
|
const videoRect = video.getBoundingClientRect();
|
|
103731
103750
|
const offsetLeft = Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0;
|
|
103732
103751
|
const offsetTop = Number.isFinite(video.offsetTop) ? video.offsetTop : 0;
|
|
@@ -103777,14 +103796,22 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
|
|
|
103777
103796
|
const active = new Set(ids);
|
|
103778
103797
|
const videos = Array.from(document.querySelectorAll("video[data-start]"));
|
|
103779
103798
|
for (const video of videos) {
|
|
103780
|
-
if (active.has(video.id)) continue;
|
|
103781
|
-
video.style.removeProperty("display");
|
|
103782
|
-
video.style.setProperty("visibility", "hidden", "important");
|
|
103783
|
-
video.style.setProperty("opacity", "0", "important");
|
|
103784
|
-
video.style.setProperty("pointer-events", "none", "important");
|
|
103785
103799
|
const img = video.nextElementSibling;
|
|
103786
|
-
|
|
103787
|
-
|
|
103800
|
+
const hasImg = img && img.classList.contains("__render_frame__");
|
|
103801
|
+
if (active.has(video.id)) {
|
|
103802
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
103803
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
103804
|
+
if (hasImg) {
|
|
103805
|
+
img.style.visibility = "visible";
|
|
103806
|
+
}
|
|
103807
|
+
} else {
|
|
103808
|
+
video.style.removeProperty("display");
|
|
103809
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
103810
|
+
video.style.setProperty("opacity", "0", "important");
|
|
103811
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
103812
|
+
if (hasImg) {
|
|
103813
|
+
img.style.visibility = "hidden";
|
|
103814
|
+
}
|
|
103788
103815
|
}
|
|
103789
103816
|
}
|
|
103790
103817
|
}, activeVideoIds);
|
|
@@ -103860,6 +103887,15 @@ function isFontResourceError(type, text, locationUrl) {
|
|
|
103860
103887
|
`${locationUrl} ${text}`
|
|
103861
103888
|
);
|
|
103862
103889
|
}
|
|
103890
|
+
async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
|
|
103891
|
+
const deadline = Date.now() + timeoutMs;
|
|
103892
|
+
while (Date.now() < deadline) {
|
|
103893
|
+
const ready = Boolean(await page.evaluate(expression));
|
|
103894
|
+
if (ready) return true;
|
|
103895
|
+
await new Promise((resolve13) => setTimeout(resolve13, intervalMs));
|
|
103896
|
+
}
|
|
103897
|
+
return Boolean(await page.evaluate(expression));
|
|
103898
|
+
}
|
|
103863
103899
|
async function initializeSession(session) {
|
|
103864
103900
|
const { page, serverUrl } = session;
|
|
103865
103901
|
page.on("console", (msg) => {
|
|
@@ -103889,14 +103925,26 @@ async function initializeSession(session) {
|
|
|
103889
103925
|
if (session.captureMode === "screenshot") {
|
|
103890
103926
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
103891
103927
|
const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout;
|
|
103892
|
-
await
|
|
103928
|
+
const pageReady2 = await pollPageExpression(
|
|
103929
|
+
page,
|
|
103893
103930
|
`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
|
|
103894
|
-
|
|
103931
|
+
pageReadyTimeout2
|
|
103895
103932
|
);
|
|
103896
|
-
|
|
103933
|
+
if (!pageReady2) {
|
|
103934
|
+
throw new Error(
|
|
103935
|
+
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
103936
|
+
);
|
|
103937
|
+
}
|
|
103938
|
+
const videosReady = await pollPageExpression(
|
|
103939
|
+
page,
|
|
103897
103940
|
`document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
|
|
103898
|
-
|
|
103941
|
+
pageReadyTimeout2
|
|
103899
103942
|
);
|
|
103943
|
+
if (!videosReady) {
|
|
103944
|
+
throw new Error(
|
|
103945
|
+
`[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
|
|
103946
|
+
);
|
|
103947
|
+
}
|
|
103900
103948
|
await page.evaluate(`document.fonts?.ready`);
|
|
103901
103949
|
session.isInitialized = true;
|
|
103902
103950
|
return;
|
|
@@ -104220,7 +104268,7 @@ var ENCODER_PRESETS = {
|
|
|
104220
104268
|
standard: { preset: "medium", quality: 18, codec: "h264" },
|
|
104221
104269
|
high: { preset: "slow", quality: 15, codec: "h264" }
|
|
104222
104270
|
};
|
|
104223
|
-
function getEncoderPreset(quality, format3 = "mp4") {
|
|
104271
|
+
function getEncoderPreset(quality, format3 = "mp4", hdr) {
|
|
104224
104272
|
const base = ENCODER_PRESETS[quality];
|
|
104225
104273
|
if (format3 === "webm") {
|
|
104226
104274
|
return {
|
|
@@ -104238,6 +104286,15 @@ function getEncoderPreset(quality, format3 = "mp4") {
|
|
|
104238
104286
|
pixelFormat: "yuva444p10le"
|
|
104239
104287
|
};
|
|
104240
104288
|
}
|
|
104289
|
+
if (hdr) {
|
|
104290
|
+
return {
|
|
104291
|
+
preset: base.preset === "ultrafast" ? "fast" : base.preset,
|
|
104292
|
+
quality: base.quality,
|
|
104293
|
+
codec: "h265",
|
|
104294
|
+
pixelFormat: "yuv420p10le",
|
|
104295
|
+
hdr
|
|
104296
|
+
};
|
|
104297
|
+
}
|
|
104241
104298
|
return { ...base, pixelFormat: "yuv420p" };
|
|
104242
104299
|
}
|
|
104243
104300
|
function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
@@ -104295,6 +104352,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
104295
104352
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
104296
104353
|
}
|
|
104297
104354
|
}
|
|
104355
|
+
if (codec === "h265") {
|
|
104356
|
+
args.push("-tag:v", "hvc1");
|
|
104357
|
+
}
|
|
104298
104358
|
} else if (codec === "vp9") {
|
|
104299
104359
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
104300
104360
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
@@ -104601,31 +104661,79 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
|
|
|
104601
104661
|
import { spawn as spawn6 } from "child_process";
|
|
104602
104662
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync4 } from "fs";
|
|
104603
104663
|
import { dirname as dirname6 } from "path";
|
|
104664
|
+
|
|
104665
|
+
// ../engine/src/utils/hdr.ts
|
|
104666
|
+
function isHdrColorSpace(cs) {
|
|
104667
|
+
if (!cs) return false;
|
|
104668
|
+
return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
|
|
104669
|
+
}
|
|
104670
|
+
var DEFAULT_HDR10_MASTERING = {
|
|
104671
|
+
masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
|
|
104672
|
+
maxCll: "1000,400"
|
|
104673
|
+
};
|
|
104674
|
+
function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
|
|
104675
|
+
const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
|
|
104676
|
+
const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
|
|
104677
|
+
const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
|
|
104678
|
+
return {
|
|
104679
|
+
colorPrimaries: "bt2020",
|
|
104680
|
+
colorTrc,
|
|
104681
|
+
colorspace: "bt2020nc",
|
|
104682
|
+
pixelFormat: "yuv420p10le",
|
|
104683
|
+
x265ColorParams: `${tagging}:${metadata}`,
|
|
104684
|
+
mastering
|
|
104685
|
+
};
|
|
104686
|
+
}
|
|
104687
|
+
function analyzeCompositionHdr(colorSpaces) {
|
|
104688
|
+
let hasPq = false;
|
|
104689
|
+
let hasHdr = false;
|
|
104690
|
+
for (const cs of colorSpaces) {
|
|
104691
|
+
if (!isHdrColorSpace(cs)) continue;
|
|
104692
|
+
hasHdr = true;
|
|
104693
|
+
if (cs?.colorTransfer === "smpte2084") hasPq = true;
|
|
104694
|
+
}
|
|
104695
|
+
if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
|
|
104696
|
+
const dominantTransfer = hasPq ? "pq" : "hlg";
|
|
104697
|
+
return { hasHdr: true, dominantTransfer };
|
|
104698
|
+
}
|
|
104699
|
+
|
|
104700
|
+
// ../engine/src/services/streamingEncoder.ts
|
|
104604
104701
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
104605
|
-
let
|
|
104606
|
-
|
|
104607
|
-
const
|
|
104608
|
-
|
|
104609
|
-
|
|
104610
|
-
|
|
104611
|
-
|
|
104612
|
-
|
|
104702
|
+
let cursor = startFrame;
|
|
104703
|
+
const pending = /* @__PURE__ */ new Map();
|
|
104704
|
+
const enqueueAt = (frame, resolve13) => {
|
|
104705
|
+
const list = pending.get(frame);
|
|
104706
|
+
if (list === void 0) {
|
|
104707
|
+
pending.set(frame, [resolve13]);
|
|
104708
|
+
} else {
|
|
104709
|
+
list.push(resolve13);
|
|
104613
104710
|
}
|
|
104614
104711
|
};
|
|
104615
|
-
|
|
104616
|
-
|
|
104617
|
-
|
|
104618
|
-
|
|
104619
|
-
|
|
104620
|
-
advanceTo: (frame) => {
|
|
104621
|
-
nextFrame = frame;
|
|
104622
|
-
resolveWaiters();
|
|
104623
|
-
},
|
|
104624
|
-
waitForAllDone: () => new Promise((resolve13) => {
|
|
104625
|
-
waiters.push({ frame: endFrame, resolve: resolve13 });
|
|
104626
|
-
resolveWaiters();
|
|
104627
|
-
})
|
|
104712
|
+
const flushAt = (frame) => {
|
|
104713
|
+
const list = pending.get(frame);
|
|
104714
|
+
if (list === void 0) return;
|
|
104715
|
+
pending.delete(frame);
|
|
104716
|
+
for (const resolve13 of list) resolve13();
|
|
104628
104717
|
};
|
|
104718
|
+
const waitForFrame = (frame) => new Promise((resolve13) => {
|
|
104719
|
+
if (frame === cursor) {
|
|
104720
|
+
resolve13();
|
|
104721
|
+
return;
|
|
104722
|
+
}
|
|
104723
|
+
enqueueAt(frame, resolve13);
|
|
104724
|
+
});
|
|
104725
|
+
const advanceTo = (frame) => {
|
|
104726
|
+
cursor = frame;
|
|
104727
|
+
flushAt(frame);
|
|
104728
|
+
};
|
|
104729
|
+
const waitForAllDone = () => new Promise((resolve13) => {
|
|
104730
|
+
if (cursor >= endFrame) {
|
|
104731
|
+
resolve13();
|
|
104732
|
+
return;
|
|
104733
|
+
}
|
|
104734
|
+
enqueueAt(endFrame, resolve13);
|
|
104735
|
+
});
|
|
104736
|
+
return { waitForFrame, advanceTo, waitForAllDone };
|
|
104629
104737
|
}
|
|
104630
104738
|
function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
104631
104739
|
const {
|
|
@@ -104638,19 +104746,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104638
104746
|
useGpu = false,
|
|
104639
104747
|
imageFormat = "jpeg"
|
|
104640
104748
|
} = options;
|
|
104641
|
-
const
|
|
104642
|
-
|
|
104643
|
-
|
|
104644
|
-
"
|
|
104645
|
-
|
|
104646
|
-
|
|
104647
|
-
|
|
104648
|
-
|
|
104649
|
-
|
|
104650
|
-
|
|
104651
|
-
|
|
104652
|
-
|
|
104653
|
-
|
|
104749
|
+
const args = [];
|
|
104750
|
+
if (options.rawInputFormat) {
|
|
104751
|
+
const hdrTransfer = options.hdr?.transfer;
|
|
104752
|
+
const inputColorTrc = hdrTransfer === "pq" ? "smpte2084" : hdrTransfer === "hlg" ? "arib-std-b67" : void 0;
|
|
104753
|
+
args.push(
|
|
104754
|
+
"-f",
|
|
104755
|
+
"rawvideo",
|
|
104756
|
+
"-pix_fmt",
|
|
104757
|
+
options.rawInputFormat,
|
|
104758
|
+
"-s",
|
|
104759
|
+
`${options.width}x${options.height}`,
|
|
104760
|
+
"-framerate",
|
|
104761
|
+
String(fps)
|
|
104762
|
+
);
|
|
104763
|
+
if (inputColorTrc) {
|
|
104764
|
+
args.push(
|
|
104765
|
+
"-color_primaries",
|
|
104766
|
+
"bt2020",
|
|
104767
|
+
"-color_trc",
|
|
104768
|
+
inputColorTrc,
|
|
104769
|
+
"-colorspace",
|
|
104770
|
+
"bt2020nc"
|
|
104771
|
+
);
|
|
104772
|
+
}
|
|
104773
|
+
args.push("-i", "-");
|
|
104774
|
+
} else {
|
|
104775
|
+
const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
|
|
104776
|
+
args.push("-f", "image2pipe", "-vcodec", inputCodec, "-framerate", String(fps), "-i", "-");
|
|
104777
|
+
}
|
|
104778
|
+
args.push("-r", String(fps));
|
|
104654
104779
|
const shouldUseGpu = useGpu && gpuEncoder !== null;
|
|
104655
104780
|
if (codec === "h264" || codec === "h265") {
|
|
104656
104781
|
if (shouldUseGpu) {
|
|
@@ -104688,12 +104813,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104688
104813
|
if (bitrate) args.push("-b:v", bitrate);
|
|
104689
104814
|
else args.push("-crf", String(quality));
|
|
104690
104815
|
const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
|
|
104691
|
-
const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
104816
|
+
const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
104692
104817
|
if (preset === "ultrafast") {
|
|
104693
104818
|
args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
|
|
104694
104819
|
} else {
|
|
104695
104820
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
104696
104821
|
}
|
|
104822
|
+
if (codec === "h265") {
|
|
104823
|
+
args.push("-tag:v", "hvc1");
|
|
104824
|
+
}
|
|
104697
104825
|
}
|
|
104698
104826
|
} else if (codec === "vp9") {
|
|
104699
104827
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
@@ -104709,17 +104837,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104709
104837
|
return [...args, "-y", outputPath];
|
|
104710
104838
|
}
|
|
104711
104839
|
if (codec === "h264" || codec === "h265") {
|
|
104712
|
-
|
|
104713
|
-
|
|
104714
|
-
|
|
104715
|
-
|
|
104716
|
-
|
|
104717
|
-
|
|
104718
|
-
|
|
104719
|
-
|
|
104720
|
-
|
|
104721
|
-
|
|
104722
|
-
|
|
104840
|
+
if (options.rawInputFormat && options.hdr) {
|
|
104841
|
+
args.push(
|
|
104842
|
+
"-colorspace:v",
|
|
104843
|
+
"bt2020nc",
|
|
104844
|
+
"-color_primaries:v",
|
|
104845
|
+
"bt2020",
|
|
104846
|
+
"-color_trc:v",
|
|
104847
|
+
options.hdr.transfer === "pq" ? "smpte2084" : "arib-std-b67",
|
|
104848
|
+
"-color_range",
|
|
104849
|
+
"tv"
|
|
104850
|
+
);
|
|
104851
|
+
} else {
|
|
104852
|
+
args.push(
|
|
104853
|
+
"-colorspace:v",
|
|
104854
|
+
"bt709",
|
|
104855
|
+
"-color_primaries:v",
|
|
104856
|
+
"bt709",
|
|
104857
|
+
"-color_trc:v",
|
|
104858
|
+
"bt709",
|
|
104859
|
+
"-color_range",
|
|
104860
|
+
"tv"
|
|
104861
|
+
);
|
|
104862
|
+
}
|
|
104863
|
+
if (options.rawInputFormat) {
|
|
104864
|
+
} else if (gpuEncoder === "vaapi") {
|
|
104723
104865
|
const vfIdx = args.indexOf("-vf");
|
|
104724
104866
|
if (vfIdx !== -1) {
|
|
104725
104867
|
args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
|
|
@@ -104789,14 +104931,16 @@ Process error: ${err.message}`;
|
|
|
104789
104931
|
if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
|
|
104790
104932
|
return false;
|
|
104791
104933
|
}
|
|
104792
|
-
|
|
104934
|
+
const copy = Buffer.from(buffer);
|
|
104935
|
+
return ffmpeg.stdin.write(copy);
|
|
104793
104936
|
},
|
|
104794
104937
|
close: async () => {
|
|
104795
104938
|
clearTimeout(timer2);
|
|
104796
104939
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
104797
|
-
|
|
104940
|
+
const stdin = ffmpeg.stdin;
|
|
104941
|
+
if (stdin && !stdin.destroyed) {
|
|
104798
104942
|
await new Promise((resolve13) => {
|
|
104799
|
-
|
|
104943
|
+
stdin.end(() => resolve13());
|
|
104800
104944
|
});
|
|
104801
104945
|
}
|
|
104802
104946
|
await exitPromise;
|
|
@@ -104900,6 +105044,10 @@ async function extractVideoMetadata(filePath) {
|
|
|
104900
105044
|
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
104901
105045
|
const fps = avgFps || rFps;
|
|
104902
105046
|
const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
|
|
105047
|
+
const colorTransfer = videoStream.color_transfer || "";
|
|
105048
|
+
const colorPrimaries = videoStream.color_primaries || "";
|
|
105049
|
+
const colorSpaceVal = videoStream.color_space || "";
|
|
105050
|
+
const hasColorInfo = !!(colorTransfer || colorPrimaries || colorSpaceVal);
|
|
104903
105051
|
return {
|
|
104904
105052
|
durationSeconds: output2.format.duration ? parseFloat(output2.format.duration) : 0,
|
|
104905
105053
|
width: videoStream.width || 0,
|
|
@@ -104907,7 +105055,8 @@ async function extractVideoMetadata(filePath) {
|
|
|
104907
105055
|
fps,
|
|
104908
105056
|
videoCodec: videoStream.codec_name || "unknown",
|
|
104909
105057
|
hasAudio: output2.streams.some((s) => s.codec_type === "audio"),
|
|
104910
|
-
isVFR
|
|
105058
|
+
isVFR,
|
|
105059
|
+
colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
|
|
104911
105060
|
};
|
|
104912
105061
|
})();
|
|
104913
105062
|
videoMetadataCache.set(filePath, probePromise);
|
|
@@ -105115,18 +105264,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
105115
105264
|
const metadata = await extractVideoMetadata(videoPath);
|
|
105116
105265
|
const framePattern = `frame_%05d.${format3}`;
|
|
105117
105266
|
const outputPattern = join8(videoOutputDir, framePattern);
|
|
105118
|
-
const
|
|
105119
|
-
|
|
105120
|
-
|
|
105121
|
-
|
|
105122
|
-
|
|
105123
|
-
|
|
105124
|
-
|
|
105125
|
-
|
|
105126
|
-
|
|
105127
|
-
"
|
|
105128
|
-
|
|
105129
|
-
|
|
105267
|
+
const isHdr = isHdrColorSpace(metadata.colorSpace);
|
|
105268
|
+
const isMacOS = process.platform === "darwin";
|
|
105269
|
+
const args = [];
|
|
105270
|
+
if (isHdr && isMacOS) {
|
|
105271
|
+
args.push("-hwaccel", "videotoolbox");
|
|
105272
|
+
}
|
|
105273
|
+
args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
|
|
105274
|
+
const vfFilters = [];
|
|
105275
|
+
if (isHdr && isMacOS) {
|
|
105276
|
+
vfFilters.push("format=nv12");
|
|
105277
|
+
}
|
|
105278
|
+
vfFilters.push(`fps=${fps}`);
|
|
105279
|
+
args.push("-vf", vfFilters.join(","));
|
|
105280
|
+
args.push("-q:v", format3 === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0");
|
|
105130
105281
|
if (format3 === "png") args.push("-compression_level", "6");
|
|
105131
105282
|
args.push("-y", outputPattern);
|
|
105132
105283
|
return new Promise((resolve13, reject) => {
|
|
@@ -105186,30 +105337,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
105186
105337
|
});
|
|
105187
105338
|
});
|
|
105188
105339
|
}
|
|
105340
|
+
async function convertSdrToHdr(inputPath, outputPath, signal, config2) {
|
|
105341
|
+
const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
105342
|
+
const args = [
|
|
105343
|
+
"-i",
|
|
105344
|
+
inputPath,
|
|
105345
|
+
"-vf",
|
|
105346
|
+
"colorspace=all=bt2020:iall=bt709:range=tv",
|
|
105347
|
+
"-color_primaries",
|
|
105348
|
+
"bt2020",
|
|
105349
|
+
"-color_trc",
|
|
105350
|
+
"arib-std-b67",
|
|
105351
|
+
"-colorspace",
|
|
105352
|
+
"bt2020nc",
|
|
105353
|
+
"-c:v",
|
|
105354
|
+
"libx264",
|
|
105355
|
+
"-preset",
|
|
105356
|
+
"fast",
|
|
105357
|
+
"-crf",
|
|
105358
|
+
"16",
|
|
105359
|
+
"-c:a",
|
|
105360
|
+
"copy",
|
|
105361
|
+
"-y",
|
|
105362
|
+
outputPath
|
|
105363
|
+
];
|
|
105364
|
+
const result = await runFfmpeg(args, { signal, timeout: timeout2 });
|
|
105365
|
+
if (!result.success) {
|
|
105366
|
+
throw new Error(
|
|
105367
|
+
`SDR\u2192HDR conversion failed (exit ${result.exitCode}): ${result.stderr.slice(-300)}`
|
|
105368
|
+
);
|
|
105369
|
+
}
|
|
105370
|
+
}
|
|
105189
105371
|
async function extractAllVideoFrames(videos, baseDir, options, signal, config2, compiledDir) {
|
|
105190
105372
|
const startTime = Date.now();
|
|
105191
105373
|
const extracted = [];
|
|
105192
105374
|
const errors = [];
|
|
105193
105375
|
let totalFramesExtracted = 0;
|
|
105376
|
+
const resolvedVideos = [];
|
|
105377
|
+
for (const video of videos) {
|
|
105378
|
+
if (signal?.aborted) break;
|
|
105379
|
+
try {
|
|
105380
|
+
let videoPath = video.src;
|
|
105381
|
+
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
105382
|
+
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
105383
|
+
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
105384
|
+
}
|
|
105385
|
+
if (isHttpUrl(videoPath)) {
|
|
105386
|
+
const downloadDir = join8(options.outputDir, "_downloads");
|
|
105387
|
+
mkdirSync5(downloadDir, { recursive: true });
|
|
105388
|
+
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
105389
|
+
}
|
|
105390
|
+
if (!existsSync8(videoPath)) {
|
|
105391
|
+
errors.push({ videoId: video.id, error: `Video file not found: ${videoPath}` });
|
|
105392
|
+
continue;
|
|
105393
|
+
}
|
|
105394
|
+
resolvedVideos.push({ video, videoPath });
|
|
105395
|
+
} catch (err) {
|
|
105396
|
+
errors.push({ videoId: video.id, error: err instanceof Error ? err.message : String(err) });
|
|
105397
|
+
}
|
|
105398
|
+
}
|
|
105399
|
+
const videoColorSpaces = await Promise.all(
|
|
105400
|
+
resolvedVideos.map(async ({ videoPath }) => {
|
|
105401
|
+
const metadata = await extractVideoMetadata(videoPath);
|
|
105402
|
+
return metadata.colorSpace;
|
|
105403
|
+
})
|
|
105404
|
+
);
|
|
105405
|
+
const hasAnyHdr = videoColorSpaces.some(isHdrColorSpace);
|
|
105406
|
+
if (hasAnyHdr) {
|
|
105407
|
+
const convertDir = join8(options.outputDir, "_hdr_normalized");
|
|
105408
|
+
mkdirSync5(convertDir, { recursive: true });
|
|
105409
|
+
for (let i = 0; i < resolvedVideos.length; i++) {
|
|
105410
|
+
if (signal?.aborted) break;
|
|
105411
|
+
const cs = videoColorSpaces[i] ?? null;
|
|
105412
|
+
if (!isHdrColorSpace(cs)) {
|
|
105413
|
+
const entry = resolvedVideos[i];
|
|
105414
|
+
if (!entry) continue;
|
|
105415
|
+
const convertedPath = join8(convertDir, `${entry.video.id}_hdr.mp4`);
|
|
105416
|
+
try {
|
|
105417
|
+
await convertSdrToHdr(entry.videoPath, convertedPath, signal, config2);
|
|
105418
|
+
entry.videoPath = convertedPath;
|
|
105419
|
+
} catch (err) {
|
|
105420
|
+
errors.push({
|
|
105421
|
+
videoId: entry.video.id,
|
|
105422
|
+
error: `SDR\u2192HDR conversion failed: ${err instanceof Error ? err.message : String(err)}`
|
|
105423
|
+
});
|
|
105424
|
+
}
|
|
105425
|
+
}
|
|
105426
|
+
}
|
|
105427
|
+
}
|
|
105194
105428
|
const results = await Promise.all(
|
|
105195
|
-
|
|
105429
|
+
resolvedVideos.map(async ({ video, videoPath }) => {
|
|
105196
105430
|
if (signal?.aborted) {
|
|
105197
105431
|
throw new Error("Video frame extraction cancelled");
|
|
105198
105432
|
}
|
|
105199
105433
|
try {
|
|
105200
|
-
let videoPath = video.src;
|
|
105201
|
-
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
105202
|
-
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
105203
|
-
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
105204
|
-
}
|
|
105205
|
-
if (isHttpUrl(videoPath)) {
|
|
105206
|
-
const downloadDir = join8(options.outputDir, "_downloads");
|
|
105207
|
-
mkdirSync5(downloadDir, { recursive: true });
|
|
105208
|
-
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
105209
|
-
}
|
|
105210
|
-
if (!existsSync8(videoPath)) {
|
|
105211
|
-
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
105212
|
-
}
|
|
105213
105434
|
let videoDuration = video.end - video.start;
|
|
105214
105435
|
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
|
|
105215
105436
|
const metadata = await extractVideoMetadata(videoPath);
|
|
@@ -105444,6 +105665,74 @@ function createVideoFrameInjector(frameLookup, config2) {
|
|
|
105444
105665
|
}
|
|
105445
105666
|
};
|
|
105446
105667
|
}
|
|
105668
|
+
async function hideVideoElements(page, videoIds) {
|
|
105669
|
+
if (videoIds.length === 0) return;
|
|
105670
|
+
await page.evaluate((ids) => {
|
|
105671
|
+
for (const id of ids) {
|
|
105672
|
+
const el = document.getElementById(id);
|
|
105673
|
+
if (el) {
|
|
105674
|
+
el.style.setProperty("visibility", "hidden", "important");
|
|
105675
|
+
el.style.setProperty("opacity", "0", "important");
|
|
105676
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
105677
|
+
if (img) img.style.setProperty("visibility", "hidden", "important");
|
|
105678
|
+
}
|
|
105679
|
+
}
|
|
105680
|
+
}, videoIds);
|
|
105681
|
+
}
|
|
105682
|
+
async function showVideoElements(page, videoIds) {
|
|
105683
|
+
if (videoIds.length === 0) return;
|
|
105684
|
+
await page.evaluate((ids) => {
|
|
105685
|
+
for (const id of ids) {
|
|
105686
|
+
const el = document.getElementById(id);
|
|
105687
|
+
if (el) {
|
|
105688
|
+
el.style.removeProperty("visibility");
|
|
105689
|
+
el.style.removeProperty("opacity");
|
|
105690
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
105691
|
+
if (img) img.style.removeProperty("visibility");
|
|
105692
|
+
}
|
|
105693
|
+
}
|
|
105694
|
+
}, videoIds);
|
|
105695
|
+
}
|
|
105696
|
+
async function queryElementStacking(page, nativeHdrVideoIds) {
|
|
105697
|
+
const hdrIds = Array.from(nativeHdrVideoIds);
|
|
105698
|
+
return page.evaluate((hdrIdList) => {
|
|
105699
|
+
const hdrSet = new Set(hdrIdList);
|
|
105700
|
+
const elements = document.querySelectorAll("[data-start]");
|
|
105701
|
+
const results = [];
|
|
105702
|
+
function getEffectiveZIndex(node) {
|
|
105703
|
+
let current = node;
|
|
105704
|
+
while (current) {
|
|
105705
|
+
const cs = window.getComputedStyle(current);
|
|
105706
|
+
const pos = cs.position;
|
|
105707
|
+
const z = parseInt(cs.zIndex);
|
|
105708
|
+
if (!Number.isNaN(z) && pos !== "static") return z;
|
|
105709
|
+
current = current.parentElement;
|
|
105710
|
+
}
|
|
105711
|
+
return 0;
|
|
105712
|
+
}
|
|
105713
|
+
for (const el of elements) {
|
|
105714
|
+
const id = el.id;
|
|
105715
|
+
if (!id) continue;
|
|
105716
|
+
const rect = el.getBoundingClientRect();
|
|
105717
|
+
const style = window.getComputedStyle(el);
|
|
105718
|
+
const zIndex = getEffectiveZIndex(el);
|
|
105719
|
+
const opacity = parseFloat(style.opacity) || 1;
|
|
105720
|
+
const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
105721
|
+
results.push({
|
|
105722
|
+
id,
|
|
105723
|
+
zIndex,
|
|
105724
|
+
x: Math.round(rect.x),
|
|
105725
|
+
y: Math.round(rect.y),
|
|
105726
|
+
width: Math.round(rect.width),
|
|
105727
|
+
height: Math.round(rect.height),
|
|
105728
|
+
opacity,
|
|
105729
|
+
visible,
|
|
105730
|
+
isHdr: hdrSet.has(id)
|
|
105731
|
+
});
|
|
105732
|
+
}
|
|
105733
|
+
return results;
|
|
105734
|
+
}, hdrIds);
|
|
105735
|
+
}
|
|
105447
105736
|
|
|
105448
105737
|
// ../engine/src/services/audioMixer.ts
|
|
105449
105738
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
@@ -105930,6 +106219,292 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
|
|
|
105930
106219
|
return totalFrames;
|
|
105931
106220
|
}
|
|
105932
106221
|
|
|
106222
|
+
// ../engine/src/utils/alphaBlit.ts
|
|
106223
|
+
import { inflateSync } from "zlib";
|
|
106224
|
+
function paeth(a, b, c) {
|
|
106225
|
+
const p = a + b - c;
|
|
106226
|
+
const pa = Math.abs(p - a);
|
|
106227
|
+
const pb = Math.abs(p - b);
|
|
106228
|
+
const pc = Math.abs(p - c);
|
|
106229
|
+
if (pa <= pb && pa <= pc) return a;
|
|
106230
|
+
if (pb <= pc) return b;
|
|
106231
|
+
return c;
|
|
106232
|
+
}
|
|
106233
|
+
function decodePngRaw(buf, caller) {
|
|
106234
|
+
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) {
|
|
106235
|
+
throw new Error(`${caller}: not a PNG file`);
|
|
106236
|
+
}
|
|
106237
|
+
let pos = 8;
|
|
106238
|
+
let width = 0;
|
|
106239
|
+
let height = 0;
|
|
106240
|
+
let bitDepth = 0;
|
|
106241
|
+
let colorType = 0;
|
|
106242
|
+
let interlace = 0;
|
|
106243
|
+
let sawIhdr = false;
|
|
106244
|
+
const idatChunks = [];
|
|
106245
|
+
while (pos + 12 <= buf.length) {
|
|
106246
|
+
const chunkLen = buf.readUInt32BE(pos);
|
|
106247
|
+
const chunkType = buf.toString("ascii", pos + 4, pos + 8);
|
|
106248
|
+
const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
|
|
106249
|
+
if (chunkType === "IHDR") {
|
|
106250
|
+
width = chunkData.readUInt32BE(0);
|
|
106251
|
+
height = chunkData.readUInt32BE(4);
|
|
106252
|
+
bitDepth = chunkData[8] ?? 0;
|
|
106253
|
+
colorType = chunkData[9] ?? 0;
|
|
106254
|
+
interlace = chunkData[12] ?? 0;
|
|
106255
|
+
sawIhdr = true;
|
|
106256
|
+
} else if (chunkType === "IDAT") {
|
|
106257
|
+
idatChunks.push(Buffer.from(chunkData));
|
|
106258
|
+
} else if (chunkType === "IEND") {
|
|
106259
|
+
break;
|
|
106260
|
+
}
|
|
106261
|
+
pos += 12 + chunkLen;
|
|
106262
|
+
}
|
|
106263
|
+
if (!sawIhdr) {
|
|
106264
|
+
throw new Error(`${caller}: PNG missing IHDR chunk`);
|
|
106265
|
+
}
|
|
106266
|
+
if (colorType !== 2 && colorType !== 6) {
|
|
106267
|
+
throw new Error(`${caller}: unsupported color type ${colorType} (expected 2=RGB or 6=RGBA)`);
|
|
106268
|
+
}
|
|
106269
|
+
if (interlace !== 0) {
|
|
106270
|
+
throw new Error(
|
|
106271
|
+
`${caller}: Adam7-interlaced PNGs are not supported (interlace method ${interlace})`
|
|
106272
|
+
);
|
|
106273
|
+
}
|
|
106274
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
106275
|
+
const bpp = channels * (bitDepth / 8);
|
|
106276
|
+
const stride = width * bpp;
|
|
106277
|
+
const compressed = Buffer.concat(idatChunks);
|
|
106278
|
+
const decompressed = inflateSync(compressed);
|
|
106279
|
+
const rawPixels = Buffer.allocUnsafe(height * stride);
|
|
106280
|
+
const prevRow = new Uint8Array(stride);
|
|
106281
|
+
const currRow = new Uint8Array(stride);
|
|
106282
|
+
let srcPos = 0;
|
|
106283
|
+
for (let y = 0; y < height; y++) {
|
|
106284
|
+
const filterType = decompressed[srcPos++] ?? 0;
|
|
106285
|
+
const rawRow = decompressed.subarray(srcPos, srcPos + stride);
|
|
106286
|
+
srcPos += stride;
|
|
106287
|
+
switch (filterType) {
|
|
106288
|
+
case 0:
|
|
106289
|
+
currRow.set(rawRow);
|
|
106290
|
+
break;
|
|
106291
|
+
case 1:
|
|
106292
|
+
for (let x = 0; x < stride; x++) {
|
|
106293
|
+
currRow[x] = (rawRow[x] ?? 0) + (x >= bpp ? currRow[x - bpp] ?? 0 : 0) & 255;
|
|
106294
|
+
}
|
|
106295
|
+
break;
|
|
106296
|
+
case 2:
|
|
106297
|
+
for (let x = 0; x < stride; x++) {
|
|
106298
|
+
currRow[x] = (rawRow[x] ?? 0) + (prevRow[x] ?? 0) & 255;
|
|
106299
|
+
}
|
|
106300
|
+
break;
|
|
106301
|
+
case 3:
|
|
106302
|
+
for (let x = 0; x < stride; x++) {
|
|
106303
|
+
const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
|
|
106304
|
+
const up = prevRow[x] ?? 0;
|
|
106305
|
+
currRow[x] = (rawRow[x] ?? 0) + Math.floor((left2 + up) / 2) & 255;
|
|
106306
|
+
}
|
|
106307
|
+
break;
|
|
106308
|
+
case 4:
|
|
106309
|
+
for (let x = 0; x < stride; x++) {
|
|
106310
|
+
const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
|
|
106311
|
+
const up = prevRow[x] ?? 0;
|
|
106312
|
+
const upLeft = x >= bpp ? prevRow[x - bpp] ?? 0 : 0;
|
|
106313
|
+
currRow[x] = (rawRow[x] ?? 0) + paeth(left2, up, upLeft) & 255;
|
|
106314
|
+
}
|
|
106315
|
+
break;
|
|
106316
|
+
default:
|
|
106317
|
+
throw new Error(`${caller}: unknown filter type ${filterType} at row ${y}`);
|
|
106318
|
+
}
|
|
106319
|
+
rawPixels.set(currRow, y * stride);
|
|
106320
|
+
prevRow.set(currRow);
|
|
106321
|
+
}
|
|
106322
|
+
return { width, height, bitDepth, colorType, rawPixels };
|
|
106323
|
+
}
|
|
106324
|
+
function decodePng(buf) {
|
|
106325
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePng");
|
|
106326
|
+
if (bitDepth !== 8) {
|
|
106327
|
+
throw new Error(`decodePng: unsupported bit depth ${bitDepth} (expected 8)`);
|
|
106328
|
+
}
|
|
106329
|
+
const output2 = new Uint8Array(width * height * 4);
|
|
106330
|
+
if (colorType === 6) {
|
|
106331
|
+
output2.set(rawPixels);
|
|
106332
|
+
} else {
|
|
106333
|
+
for (let i = 0; i < width * height; i++) {
|
|
106334
|
+
output2[i * 4 + 0] = rawPixels[i * 3 + 0] ?? 0;
|
|
106335
|
+
output2[i * 4 + 1] = rawPixels[i * 3 + 1] ?? 0;
|
|
106336
|
+
output2[i * 4 + 2] = rawPixels[i * 3 + 2] ?? 0;
|
|
106337
|
+
output2[i * 4 + 3] = 255;
|
|
106338
|
+
}
|
|
106339
|
+
}
|
|
106340
|
+
return { width, height, data: output2 };
|
|
106341
|
+
}
|
|
106342
|
+
function decodePngToRgb48le(buf) {
|
|
106343
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePngToRgb48le");
|
|
106344
|
+
if (bitDepth !== 16) {
|
|
106345
|
+
throw new Error(`decodePngToRgb48le: unsupported bit depth ${bitDepth} (expected 16)`);
|
|
106346
|
+
}
|
|
106347
|
+
const bpp = colorType === 6 ? 8 : 6;
|
|
106348
|
+
const output2 = Buffer.allocUnsafe(width * height * 6);
|
|
106349
|
+
for (let y = 0; y < height; y++) {
|
|
106350
|
+
const dstBase = y * width * 6;
|
|
106351
|
+
const srcRowBase = y * width * bpp;
|
|
106352
|
+
for (let x = 0; x < width; x++) {
|
|
106353
|
+
const srcBase = srcRowBase + x * bpp;
|
|
106354
|
+
output2[dstBase + x * 6 + 0] = rawPixels[srcBase + 1] ?? 0;
|
|
106355
|
+
output2[dstBase + x * 6 + 1] = rawPixels[srcBase + 0] ?? 0;
|
|
106356
|
+
output2[dstBase + x * 6 + 2] = rawPixels[srcBase + 3] ?? 0;
|
|
106357
|
+
output2[dstBase + x * 6 + 3] = rawPixels[srcBase + 2] ?? 0;
|
|
106358
|
+
output2[dstBase + x * 6 + 4] = rawPixels[srcBase + 5] ?? 0;
|
|
106359
|
+
output2[dstBase + x * 6 + 5] = rawPixels[srcBase + 4] ?? 0;
|
|
106360
|
+
}
|
|
106361
|
+
}
|
|
106362
|
+
return { width, height, data: output2 };
|
|
106363
|
+
}
|
|
106364
|
+
function buildSrgbToHdrLut(transfer) {
|
|
106365
|
+
const lut = new Uint16Array(256);
|
|
106366
|
+
const hlgA = 0.17883277;
|
|
106367
|
+
const hlgB = 1 - 4 * hlgA;
|
|
106368
|
+
const hlgC = 0.5 - hlgA * Math.log(4 * hlgA);
|
|
106369
|
+
const pqM1 = 0.1593017578125;
|
|
106370
|
+
const pqM2 = 78.84375;
|
|
106371
|
+
const pqC1 = 0.8359375;
|
|
106372
|
+
const pqC2 = 18.8515625;
|
|
106373
|
+
const pqC3 = 18.6875;
|
|
106374
|
+
const pqMaxNits = 1e4;
|
|
106375
|
+
const sdrNits = 203;
|
|
106376
|
+
for (let i = 0; i < 256; i++) {
|
|
106377
|
+
const v = i / 255;
|
|
106378
|
+
const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
106379
|
+
let signal;
|
|
106380
|
+
if (transfer === "hlg") {
|
|
106381
|
+
signal = linear <= 1 / 12 ? Math.sqrt(3 * linear) : hlgA * Math.log(12 * linear - hlgB) + hlgC;
|
|
106382
|
+
} else {
|
|
106383
|
+
const Lp = Math.max(0, linear * sdrNits / pqMaxNits);
|
|
106384
|
+
const Lm1 = Math.pow(Lp, pqM1);
|
|
106385
|
+
signal = Math.pow((pqC1 + pqC2 * Lm1) / (1 + pqC3 * Lm1), pqM2);
|
|
106386
|
+
}
|
|
106387
|
+
lut[i] = Math.min(65535, Math.round(signal * 65535));
|
|
106388
|
+
}
|
|
106389
|
+
return lut;
|
|
106390
|
+
}
|
|
106391
|
+
var SRGB_TO_HLG = buildSrgbToHdrLut("hlg");
|
|
106392
|
+
var SRGB_TO_PQ = buildSrgbToHdrLut("pq");
|
|
106393
|
+
function getSrgbToHdrLut(transfer) {
|
|
106394
|
+
return transfer === "pq" ? SRGB_TO_PQ : SRGB_TO_HLG;
|
|
106395
|
+
}
|
|
106396
|
+
function blitRgba8OverRgb48le(domRgba, canvas, width, height, transfer = "hlg") {
|
|
106397
|
+
const pixelCount = width * height;
|
|
106398
|
+
const lut = getSrgbToHdrLut(transfer);
|
|
106399
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
106400
|
+
const da = domRgba[i * 4 + 3] ?? 0;
|
|
106401
|
+
if (da === 0) {
|
|
106402
|
+
continue;
|
|
106403
|
+
} else if (da === 255) {
|
|
106404
|
+
const r16 = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
|
|
106405
|
+
const g16 = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
|
|
106406
|
+
const b16 = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
|
|
106407
|
+
canvas.writeUInt16LE(r16, i * 6);
|
|
106408
|
+
canvas.writeUInt16LE(g16, i * 6 + 2);
|
|
106409
|
+
canvas.writeUInt16LE(b16, i * 6 + 4);
|
|
106410
|
+
} else {
|
|
106411
|
+
const alpha = da / 255;
|
|
106412
|
+
const invAlpha = 1 - alpha;
|
|
106413
|
+
const hdrR = (canvas[i * 6 + 0] ?? 0) | (canvas[i * 6 + 1] ?? 0) << 8;
|
|
106414
|
+
const hdrG = (canvas[i * 6 + 2] ?? 0) | (canvas[i * 6 + 3] ?? 0) << 8;
|
|
106415
|
+
const hdrB = (canvas[i * 6 + 4] ?? 0) | (canvas[i * 6 + 5] ?? 0) << 8;
|
|
106416
|
+
const domR = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
|
|
106417
|
+
const domG = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
|
|
106418
|
+
const domB = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
|
|
106419
|
+
canvas.writeUInt16LE(Math.round(domR * alpha + hdrR * invAlpha), i * 6);
|
|
106420
|
+
canvas.writeUInt16LE(Math.round(domG * alpha + hdrG * invAlpha), i * 6 + 2);
|
|
106421
|
+
canvas.writeUInt16LE(Math.round(domB * alpha + hdrB * invAlpha), i * 6 + 4);
|
|
106422
|
+
}
|
|
106423
|
+
}
|
|
106424
|
+
}
|
|
106425
|
+
function cornerAlpha(px, py, cx, cy, r) {
|
|
106426
|
+
const dx = px - cx;
|
|
106427
|
+
const dy = py - cy;
|
|
106428
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
106429
|
+
if (dist > r + 0.5) return 0;
|
|
106430
|
+
if (dist > r - 0.5) return r + 0.5 - dist;
|
|
106431
|
+
return 1;
|
|
106432
|
+
}
|
|
106433
|
+
function roundedRectAlpha(px, py, w, h, radii) {
|
|
106434
|
+
const [tl, tr, br, bl] = radii;
|
|
106435
|
+
if (px < tl && py < tl) return cornerAlpha(px, py, tl, tl, tl);
|
|
106436
|
+
if (px >= w - tr && py < tr) return cornerAlpha(px, py, w - tr, tr, tr);
|
|
106437
|
+
if (px >= w - br && py >= h - br) return cornerAlpha(px, py, w - br, h - br, br);
|
|
106438
|
+
if (px < bl && py >= h - bl) return cornerAlpha(px, py, bl, h - bl, bl);
|
|
106439
|
+
return 1;
|
|
106440
|
+
}
|
|
106441
|
+
function blitRgb48leRegion(canvas, source2, dx, dy, sw, sh, canvasWidth, canvasHeight, opacity, borderRadius) {
|
|
106442
|
+
if (sw <= 0 || sh <= 0) return;
|
|
106443
|
+
const op = opacity ?? 1;
|
|
106444
|
+
const x0 = Math.max(0, dx);
|
|
106445
|
+
const y0 = Math.max(0, dy);
|
|
106446
|
+
const x1 = Math.min(canvasWidth, dx + sw);
|
|
106447
|
+
const y1 = Math.min(canvasHeight, dy + sh);
|
|
106448
|
+
if (x0 >= x1 || y0 >= y1) return;
|
|
106449
|
+
const clippedW = x1 - x0;
|
|
106450
|
+
const srcOffsetX = x0 - dx;
|
|
106451
|
+
const srcOffsetY = y0 - dy;
|
|
106452
|
+
const hasMask = borderRadius !== void 0;
|
|
106453
|
+
if (op >= 0.999 && !hasMask) {
|
|
106454
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
106455
|
+
const srcRowOff = ((srcOffsetY + y) * sw + srcOffsetX) * 6;
|
|
106456
|
+
const dstRowOff = ((y0 + y) * canvasWidth + x0) * 6;
|
|
106457
|
+
source2.copy(canvas, dstRowOff, srcRowOff, srcRowOff + clippedW * 6);
|
|
106458
|
+
}
|
|
106459
|
+
} else {
|
|
106460
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
106461
|
+
for (let x = 0; x < clippedW; x++) {
|
|
106462
|
+
let effectiveOp = op;
|
|
106463
|
+
if (hasMask) {
|
|
106464
|
+
const ma = roundedRectAlpha(srcOffsetX + x, srcOffsetY + y, sw, sh, borderRadius);
|
|
106465
|
+
if (ma <= 0) continue;
|
|
106466
|
+
effectiveOp *= ma;
|
|
106467
|
+
}
|
|
106468
|
+
const srcOff = ((srcOffsetY + y) * sw + srcOffsetX + x) * 6;
|
|
106469
|
+
const dstOff = ((y0 + y) * canvasWidth + x0 + x) * 6;
|
|
106470
|
+
if (effectiveOp >= 0.999) {
|
|
106471
|
+
source2.copy(canvas, dstOff, srcOff, srcOff + 6);
|
|
106472
|
+
} else {
|
|
106473
|
+
const invEff = 1 - effectiveOp;
|
|
106474
|
+
const sr = source2.readUInt16LE(srcOff);
|
|
106475
|
+
const sg = source2.readUInt16LE(srcOff + 2);
|
|
106476
|
+
const sb = source2.readUInt16LE(srcOff + 4);
|
|
106477
|
+
const dr = canvas.readUInt16LE(dstOff);
|
|
106478
|
+
const dg = canvas.readUInt16LE(dstOff + 2);
|
|
106479
|
+
const db = canvas.readUInt16LE(dstOff + 4);
|
|
106480
|
+
canvas.writeUInt16LE(Math.round(sr * effectiveOp + dr * invEff), dstOff);
|
|
106481
|
+
canvas.writeUInt16LE(Math.round(sg * effectiveOp + dg * invEff), dstOff + 2);
|
|
106482
|
+
canvas.writeUInt16LE(Math.round(sb * effectiveOp + db * invEff), dstOff + 4);
|
|
106483
|
+
}
|
|
106484
|
+
}
|
|
106485
|
+
}
|
|
106486
|
+
}
|
|
106487
|
+
}
|
|
106488
|
+
|
|
106489
|
+
// ../engine/src/utils/layerCompositor.ts
|
|
106490
|
+
function groupIntoLayers(elements) {
|
|
106491
|
+
const sorted = [...elements].sort((a, b) => a.zIndex - b.zIndex);
|
|
106492
|
+
const layers = [];
|
|
106493
|
+
for (const el of sorted) {
|
|
106494
|
+
if (el.isHdr) {
|
|
106495
|
+
layers.push({ type: "hdr", element: el });
|
|
106496
|
+
} else {
|
|
106497
|
+
const last2 = layers[layers.length - 1];
|
|
106498
|
+
if (last2 && last2.type === "dom") {
|
|
106499
|
+
last2.elementIds.push(el.id);
|
|
106500
|
+
} else {
|
|
106501
|
+
layers.push({ type: "dom", elementIds: [el.id] });
|
|
106502
|
+
}
|
|
106503
|
+
}
|
|
106504
|
+
}
|
|
106505
|
+
return layers;
|
|
106506
|
+
}
|
|
106507
|
+
|
|
105933
106508
|
// src/services/renderOrchestrator.ts
|
|
105934
106509
|
import { join as join15, dirname as dirname10, resolve as resolve10 } from "path";
|
|
105935
106510
|
import { randomUUID } from "crypto";
|
|
@@ -106036,6 +106611,102 @@ var MIME_TYPES = {
|
|
|
106036
106611
|
".ttf": "font/ttf",
|
|
106037
106612
|
".otf": "font/otf"
|
|
106038
106613
|
};
|
|
106614
|
+
var VIRTUAL_TIME_SHIM = String.raw`(function() {
|
|
106615
|
+
if (window.__HF_VIRTUAL_TIME__) return;
|
|
106616
|
+
|
|
106617
|
+
var virtualNowMs = 0;
|
|
106618
|
+
var rafId = 1;
|
|
106619
|
+
var rafQueue = [];
|
|
106620
|
+
var OriginalDate = Date;
|
|
106621
|
+
var originalSetTimeout = window.setTimeout.bind(window);
|
|
106622
|
+
var originalClearTimeout = window.clearTimeout.bind(window);
|
|
106623
|
+
var originalSetInterval = window.setInterval.bind(window);
|
|
106624
|
+
var originalClearInterval = window.clearInterval.bind(window);
|
|
106625
|
+
var originalRequestAnimationFrame = window.requestAnimationFrame
|
|
106626
|
+
? window.requestAnimationFrame.bind(window)
|
|
106627
|
+
: null;
|
|
106628
|
+
var originalCancelAnimationFrame = window.cancelAnimationFrame
|
|
106629
|
+
? window.cancelAnimationFrame.bind(window)
|
|
106630
|
+
: null;
|
|
106631
|
+
|
|
106632
|
+
function flushAnimationFrame() {
|
|
106633
|
+
if (!rafQueue.length) return;
|
|
106634
|
+
var current = rafQueue.slice();
|
|
106635
|
+
rafQueue.length = 0;
|
|
106636
|
+
for (var i = 0; i < current.length; i++) {
|
|
106637
|
+
var entry = current[i];
|
|
106638
|
+
if (entry.cancelled) continue;
|
|
106639
|
+
try {
|
|
106640
|
+
entry.callback(virtualNowMs);
|
|
106641
|
+
} catch {}
|
|
106642
|
+
}
|
|
106643
|
+
}
|
|
106644
|
+
|
|
106645
|
+
function VirtualDate() {
|
|
106646
|
+
var args = Array.prototype.slice.call(arguments);
|
|
106647
|
+
if (!(this instanceof VirtualDate)) {
|
|
106648
|
+
return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
|
|
106649
|
+
}
|
|
106650
|
+
var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
|
|
106651
|
+
Object.setPrototypeOf(instance, VirtualDate.prototype);
|
|
106652
|
+
return instance;
|
|
106653
|
+
}
|
|
106654
|
+
|
|
106655
|
+
VirtualDate.prototype = OriginalDate.prototype;
|
|
106656
|
+
Object.setPrototypeOf(VirtualDate, OriginalDate);
|
|
106657
|
+
VirtualDate.now = function() { return virtualNowMs; };
|
|
106658
|
+
VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
|
|
106659
|
+
VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
|
|
106660
|
+
|
|
106661
|
+
try {
|
|
106662
|
+
Object.defineProperty(window, "Date", {
|
|
106663
|
+
configurable: true,
|
|
106664
|
+
writable: true,
|
|
106665
|
+
value: VirtualDate,
|
|
106666
|
+
});
|
|
106667
|
+
} catch {}
|
|
106668
|
+
|
|
106669
|
+
if (window.performance && typeof window.performance.now === "function") {
|
|
106670
|
+
try {
|
|
106671
|
+
Object.defineProperty(window.performance, "now", {
|
|
106672
|
+
configurable: true,
|
|
106673
|
+
value: function() { return virtualNowMs; },
|
|
106674
|
+
});
|
|
106675
|
+
} catch {}
|
|
106676
|
+
}
|
|
106677
|
+
|
|
106678
|
+
window.requestAnimationFrame = function(callback) {
|
|
106679
|
+
if (typeof callback !== "function") return 0;
|
|
106680
|
+
var entry = { id: rafId++, callback: callback, cancelled: false };
|
|
106681
|
+
rafQueue.push(entry);
|
|
106682
|
+
return entry.id;
|
|
106683
|
+
};
|
|
106684
|
+
window.cancelAnimationFrame = function(id) {
|
|
106685
|
+
for (var i = 0; i < rafQueue.length; i++) {
|
|
106686
|
+
if (rafQueue[i].id === id) {
|
|
106687
|
+
rafQueue[i].cancelled = true;
|
|
106688
|
+
}
|
|
106689
|
+
}
|
|
106690
|
+
};
|
|
106691
|
+
|
|
106692
|
+
window.__HF_VIRTUAL_TIME__ = {
|
|
106693
|
+
originalSetTimeout: originalSetTimeout,
|
|
106694
|
+
originalClearTimeout: originalClearTimeout,
|
|
106695
|
+
originalSetInterval: originalSetInterval,
|
|
106696
|
+
originalClearInterval: originalClearInterval,
|
|
106697
|
+
originalRequestAnimationFrame: originalRequestAnimationFrame,
|
|
106698
|
+
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
106699
|
+
seekToTime: function(nextTimeMs) {
|
|
106700
|
+
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
106701
|
+
virtualNowMs = safeTimeMs;
|
|
106702
|
+
flushAnimationFrame();
|
|
106703
|
+
return virtualNowMs;
|
|
106704
|
+
},
|
|
106705
|
+
getTime: function() {
|
|
106706
|
+
return virtualNowMs;
|
|
106707
|
+
},
|
|
106708
|
+
};
|
|
106709
|
+
})();`;
|
|
106039
106710
|
var RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
|
|
106040
106711
|
var RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
|
|
106041
106712
|
var RENDER_SEEK_STEP = Math.max(
|
|
@@ -106047,6 +106718,10 @@ var RENDER_SEEK_OFFSET_FRACTION = Math.max(
|
|
|
106047
106718
|
Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
|
|
106048
106719
|
);
|
|
106049
106720
|
var RENDER_MODE_SCRIPT = `(function() {
|
|
106721
|
+
var __realSetTimeout =
|
|
106722
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
|
|
106723
|
+
? window.__HF_VIRTUAL_TIME__.originalSetTimeout
|
|
106724
|
+
: window.setTimeout.bind(window);
|
|
106050
106725
|
var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
|
|
106051
106726
|
var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
|
|
106052
106727
|
var __seekStep = ${RENDER_SEEK_STEP};
|
|
@@ -106140,40 +106815,88 @@ var RENDER_MODE_SCRIPT = `(function() {
|
|
|
106140
106815
|
window.__renderReady = true;
|
|
106141
106816
|
return;
|
|
106142
106817
|
}
|
|
106143
|
-
|
|
106818
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
106144
106819
|
return;
|
|
106145
106820
|
}
|
|
106146
106821
|
if (installMediaFallbackPlayer()) {
|
|
106147
106822
|
return;
|
|
106148
106823
|
}
|
|
106149
|
-
|
|
106824
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
106150
106825
|
}
|
|
106151
106826
|
waitForPlayer();
|
|
106152
106827
|
})();`;
|
|
106828
|
+
var HF_EARLY_STUB = `(function() {
|
|
106829
|
+
if (typeof window === "undefined") return;
|
|
106830
|
+
if (!window.__hf) window.__hf = {};
|
|
106831
|
+
})();`;
|
|
106153
106832
|
var HF_BRIDGE_SCRIPT = `(function() {
|
|
106833
|
+
var __realSetInterval =
|
|
106834
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
|
|
106835
|
+
? window.__HF_VIRTUAL_TIME__.originalSetInterval
|
|
106836
|
+
: window.setInterval.bind(window);
|
|
106837
|
+
var __realClearInterval =
|
|
106838
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
|
|
106839
|
+
? window.__HF_VIRTUAL_TIME__.originalClearInterval
|
|
106840
|
+
: window.clearInterval.bind(window);
|
|
106154
106841
|
function getDeclaredDuration() {
|
|
106155
106842
|
var root = document.querySelector('[data-composition-id]');
|
|
106156
106843
|
if (!root) return 0;
|
|
106157
106844
|
var d = Number(root.getAttribute('data-duration'));
|
|
106158
106845
|
return Number.isFinite(d) && d > 0 ? d : 0;
|
|
106159
106846
|
}
|
|
106847
|
+
function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
|
|
106848
|
+
var frames;
|
|
106849
|
+
try {
|
|
106850
|
+
frames = frameWindow.frames;
|
|
106851
|
+
} catch (_error) {
|
|
106852
|
+
return;
|
|
106853
|
+
}
|
|
106854
|
+
if (!frames || typeof frames.length !== "number") return;
|
|
106855
|
+
for (var i = 0; i < frames.length; i++) {
|
|
106856
|
+
var childWindow = null;
|
|
106857
|
+
try {
|
|
106858
|
+
childWindow = frames[i];
|
|
106859
|
+
if (!childWindow || childWindow === frameWindow) continue;
|
|
106860
|
+
if (
|
|
106861
|
+
childWindow.__HF_VIRTUAL_TIME__ &&
|
|
106862
|
+
typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
|
|
106863
|
+
) {
|
|
106864
|
+
childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
106865
|
+
}
|
|
106866
|
+
} catch (_error) {
|
|
106867
|
+
continue;
|
|
106868
|
+
}
|
|
106869
|
+
seekSameOriginChildFrames(childWindow, nextTimeMs);
|
|
106870
|
+
}
|
|
106871
|
+
}
|
|
106160
106872
|
function bridge() {
|
|
106161
106873
|
var p = window.__player;
|
|
106162
106874
|
if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
|
|
106163
106875
|
return false;
|
|
106164
106876
|
}
|
|
106165
|
-
window.__hf
|
|
106166
|
-
|
|
106877
|
+
var hf = window.__hf || {};
|
|
106878
|
+
Object.defineProperty(hf, "duration", {
|
|
106879
|
+
configurable: true,
|
|
106880
|
+
enumerable: true,
|
|
106881
|
+
get: function() {
|
|
106167
106882
|
var d = p.getDuration();
|
|
106168
106883
|
return d > 0 ? d : getDeclaredDuration();
|
|
106169
106884
|
},
|
|
106170
|
-
|
|
106885
|
+
});
|
|
106886
|
+
hf.seek = function(t) {
|
|
106887
|
+
p.renderSeek(t);
|
|
106888
|
+
var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
|
|
106889
|
+
if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
|
|
106890
|
+
window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
106891
|
+
}
|
|
106892
|
+
seekSameOriginChildFrames(window, nextTimeMs);
|
|
106171
106893
|
};
|
|
106894
|
+
window.__hf = hf;
|
|
106172
106895
|
return true;
|
|
106173
106896
|
}
|
|
106174
106897
|
if (bridge()) return;
|
|
106175
|
-
var iv =
|
|
106176
|
-
if (bridge())
|
|
106898
|
+
var iv = __realSetInterval(function() {
|
|
106899
|
+
if (bridge()) __realClearInterval(iv);
|
|
106177
106900
|
}, 50);
|
|
106178
106901
|
})();`;
|
|
106179
106902
|
function stripEmbeddedRuntimeScripts(html) {
|
|
@@ -106235,8 +106958,22 @@ function injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbedded) {
|
|
|
106235
106958
|
}
|
|
106236
106959
|
return html;
|
|
106237
106960
|
}
|
|
106961
|
+
function injectScriptsAtHeadStart(html, scripts) {
|
|
106962
|
+
if (scripts.length === 0) return html;
|
|
106963
|
+
const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
|
|
106964
|
+
if (html.includes("<head")) {
|
|
106965
|
+
return html.replace(/<head\b[^>]*>/i, (match2) => `${match2}
|
|
106966
|
+
${headTags}`);
|
|
106967
|
+
}
|
|
106968
|
+
if (html.includes("<body")) {
|
|
106969
|
+
return html.replace("<body", () => `${headTags}
|
|
106970
|
+
<body`);
|
|
106971
|
+
}
|
|
106972
|
+
return headTags + "\n" + html;
|
|
106973
|
+
}
|
|
106238
106974
|
function createFileServer2(options) {
|
|
106239
106975
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
106976
|
+
const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
|
|
106240
106977
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
106241
106978
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
106242
106979
|
const app = new Hono2();
|
|
@@ -106260,7 +106997,11 @@ function createFileServer2(options) {
|
|
|
106260
106997
|
if (ext === ".html") {
|
|
106261
106998
|
const rawHtml = readFileSync6(filePath, "utf-8");
|
|
106262
106999
|
const isIndex = relativePath === "index.html";
|
|
106263
|
-
|
|
107000
|
+
let html = rawHtml;
|
|
107001
|
+
if (preHeadScripts.length > 0) {
|
|
107002
|
+
html = injectScriptsAtHeadStart(html, preHeadScripts);
|
|
107003
|
+
}
|
|
107004
|
+
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
106264
107005
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
106265
107006
|
}
|
|
106266
107007
|
const content = readFileSync6(filePath);
|
|
@@ -106708,6 +107449,37 @@ function dedupeElementsById(elements) {
|
|
|
106708
107449
|
}
|
|
106709
107450
|
return Array.from(deduped.values());
|
|
106710
107451
|
}
|
|
107452
|
+
var INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
107453
|
+
function stripJsComments(source2) {
|
|
107454
|
+
return source2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
107455
|
+
}
|
|
107456
|
+
function detectRenderModeHints(html) {
|
|
107457
|
+
const reasons = [];
|
|
107458
|
+
const { document: document2 } = parseHTML(html);
|
|
107459
|
+
if (document2.querySelector("iframe")) {
|
|
107460
|
+
reasons.push({
|
|
107461
|
+
code: "iframe",
|
|
107462
|
+
message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
|
|
107463
|
+
});
|
|
107464
|
+
}
|
|
107465
|
+
let scriptMatch;
|
|
107466
|
+
const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
|
|
107467
|
+
while ((scriptMatch = scriptPattern.exec(html)) !== null) {
|
|
107468
|
+
const attrs = scriptMatch[1] || "";
|
|
107469
|
+
if (/\bsrc\s*=/i.test(attrs)) continue;
|
|
107470
|
+
const content = stripJsComments(scriptMatch[2] || "");
|
|
107471
|
+
if (!/requestAnimationFrame\s*\(/.test(content)) continue;
|
|
107472
|
+
reasons.push({
|
|
107473
|
+
code: "requestAnimationFrame",
|
|
107474
|
+
message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
|
|
107475
|
+
});
|
|
107476
|
+
break;
|
|
107477
|
+
}
|
|
107478
|
+
return {
|
|
107479
|
+
recommendScreenshot: reasons.length > 0,
|
|
107480
|
+
reasons
|
|
107481
|
+
};
|
|
107482
|
+
}
|
|
106711
107483
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
106712
107484
|
let filePath = src;
|
|
106713
107485
|
if (isHttpUrl(src)) {
|
|
@@ -107271,6 +108043,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
107271
108043
|
/(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
|
|
107272
108044
|
"$1"
|
|
107273
108045
|
);
|
|
108046
|
+
const renderModeHints = detectRenderModeHints(sanitizedHtml);
|
|
107274
108047
|
const coalescedHtml = await injectDeterministicFontFaces(
|
|
107275
108048
|
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
107276
108049
|
);
|
|
@@ -107314,7 +108087,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
107314
108087
|
externalAssets,
|
|
107315
108088
|
width,
|
|
107316
108089
|
height,
|
|
107317
|
-
staticDuration
|
|
108090
|
+
staticDuration,
|
|
108091
|
+
renderModeHints
|
|
107318
108092
|
};
|
|
107319
108093
|
}
|
|
107320
108094
|
async function discoverMediaFromBrowser(page) {
|
|
@@ -107408,7 +108182,8 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
|
|
|
107408
108182
|
subCompositions,
|
|
107409
108183
|
videos,
|
|
107410
108184
|
audios,
|
|
107411
|
-
unresolvedCompositions: remaining
|
|
108185
|
+
unresolvedCompositions: remaining,
|
|
108186
|
+
renderModeHints: compiled.renderModeHints
|
|
107412
108187
|
};
|
|
107413
108188
|
}
|
|
107414
108189
|
|
|
@@ -107458,6 +108233,24 @@ async function safeCleanup(label, fn, log = defaultLogger) {
|
|
|
107458
108233
|
});
|
|
107459
108234
|
}
|
|
107460
108235
|
}
|
|
108236
|
+
var frameDirMaxIndexCache = /* @__PURE__ */ new Map();
|
|
108237
|
+
var FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
|
|
108238
|
+
function getMaxFrameIndex(frameDir) {
|
|
108239
|
+
const cached = frameDirMaxIndexCache.get(frameDir);
|
|
108240
|
+
if (cached !== void 0) return cached;
|
|
108241
|
+
let max = 0;
|
|
108242
|
+
try {
|
|
108243
|
+
for (const name of readdirSync6(frameDir)) {
|
|
108244
|
+
const m = FRAME_FILENAME_RE.exec(name);
|
|
108245
|
+
if (!m) continue;
|
|
108246
|
+
const n = Number(m[1]);
|
|
108247
|
+
if (Number.isFinite(n) && n > max) max = n;
|
|
108248
|
+
}
|
|
108249
|
+
} catch {
|
|
108250
|
+
}
|
|
108251
|
+
frameDirMaxIndexCache.set(frameDir, max);
|
|
108252
|
+
return max;
|
|
108253
|
+
}
|
|
107461
108254
|
var RenderCancelledError = class extends Error {
|
|
107462
108255
|
reason;
|
|
107463
108256
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -107545,11 +108338,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
107545
108338
|
end: a.end,
|
|
107546
108339
|
mediaStart: a.mediaStart
|
|
107547
108340
|
})),
|
|
107548
|
-
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
108341
|
+
subCompositions: Array.from(compiled.subCompositions.keys()),
|
|
108342
|
+
renderModeHints: compiled.renderModeHints
|
|
107549
108343
|
};
|
|
107550
108344
|
writeFileSync4(join15(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
107551
108345
|
}
|
|
107552
108346
|
}
|
|
108347
|
+
function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
108348
|
+
if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
|
|
108349
|
+
cfg.forceScreenshot = true;
|
|
108350
|
+
log.warn("Auto-selected screenshot capture mode for render compatibility", {
|
|
108351
|
+
reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
|
|
108352
|
+
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
108353
|
+
});
|
|
108354
|
+
}
|
|
107553
108355
|
function createRenderJob(config2) {
|
|
107554
108356
|
return {
|
|
107555
108357
|
id: randomUUID(),
|
|
@@ -107662,6 +108464,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107662
108464
|
let compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"));
|
|
107663
108465
|
assertNotAborted();
|
|
107664
108466
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
108467
|
+
applyRenderModeHints(cfg, compiled, log);
|
|
107665
108468
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
107666
108469
|
log.info("Compiled composition metadata", {
|
|
107667
108470
|
entryFile,
|
|
@@ -107669,7 +108472,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107669
108472
|
width: compiled.width,
|
|
107670
108473
|
height: compiled.height,
|
|
107671
108474
|
videoCount: compiled.videos.length,
|
|
107672
|
-
audioCount: compiled.audios.length
|
|
108475
|
+
audioCount: compiled.audios.length,
|
|
108476
|
+
renderModeHints: compiled.renderModeHints
|
|
107673
108477
|
});
|
|
107674
108478
|
const composition = {
|
|
107675
108479
|
duration: compiled.staticDuration,
|
|
@@ -107689,7 +108493,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107689
108493
|
fileServer = await createFileServer2({
|
|
107690
108494
|
projectDir,
|
|
107691
108495
|
compiledDir: join15(workDir, "compiled"),
|
|
107692
|
-
port: 0
|
|
108496
|
+
port: 0,
|
|
108497
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
107693
108498
|
});
|
|
107694
108499
|
assertNotAborted();
|
|
107695
108500
|
const captureOpts = {
|
|
@@ -107845,7 +108650,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107845
108650
|
}
|
|
107846
108651
|
}
|
|
107847
108652
|
}
|
|
107848
|
-
} catch {
|
|
108653
|
+
} catch (err) {
|
|
108654
|
+
log.warn("Failed to gather browser diagnostics for zero-duration composition", {
|
|
108655
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108656
|
+
});
|
|
107849
108657
|
diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
|
|
107850
108658
|
}
|
|
107851
108659
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
@@ -107869,8 +108677,26 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107869
108677
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
107870
108678
|
let frameLookup = null;
|
|
107871
108679
|
const compiledDir = join15(workDir, "compiled");
|
|
108680
|
+
let extractionResult = null;
|
|
108681
|
+
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
108682
|
+
if (composition.videos.length > 0) {
|
|
108683
|
+
await Promise.all(
|
|
108684
|
+
composition.videos.map(async (v) => {
|
|
108685
|
+
let videoPath = v.src;
|
|
108686
|
+
if (!videoPath.startsWith("/")) {
|
|
108687
|
+
const fromCompiled = existsSync15(join15(compiledDir, videoPath)) ? join15(compiledDir, videoPath) : join15(projectDir, videoPath);
|
|
108688
|
+
videoPath = fromCompiled;
|
|
108689
|
+
}
|
|
108690
|
+
if (!existsSync15(videoPath)) return;
|
|
108691
|
+
const meta = await extractVideoMetadata(videoPath);
|
|
108692
|
+
if (isHdrColorSpace(meta.colorSpace)) {
|
|
108693
|
+
nativeHdrVideoIds.add(v.id);
|
|
108694
|
+
}
|
|
108695
|
+
})
|
|
108696
|
+
);
|
|
108697
|
+
}
|
|
107872
108698
|
if (composition.videos.length > 0) {
|
|
107873
|
-
|
|
108699
|
+
extractionResult = await extractAllVideoFrames(
|
|
107874
108700
|
composition.videos,
|
|
107875
108701
|
projectDir,
|
|
107876
108702
|
{ fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
|
|
@@ -107905,6 +108731,23 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107905
108731
|
} else {
|
|
107906
108732
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
107907
108733
|
}
|
|
108734
|
+
let effectiveHdr;
|
|
108735
|
+
if (frameLookup) {
|
|
108736
|
+
const colorSpaces = (extractionResult?.extracted ?? []).map((ext) => ext.metadata.colorSpace);
|
|
108737
|
+
const info = analyzeCompositionHdr(colorSpaces);
|
|
108738
|
+
if (info.hasHdr && info.dominantTransfer) {
|
|
108739
|
+
effectiveHdr = { transfer: info.dominantTransfer };
|
|
108740
|
+
}
|
|
108741
|
+
}
|
|
108742
|
+
if (effectiveHdr && outputFormat !== "mp4") {
|
|
108743
|
+
log.info(`[Render] HDR source detected but format is ${outputFormat} \u2014 using SDR`);
|
|
108744
|
+
effectiveHdr = void 0;
|
|
108745
|
+
}
|
|
108746
|
+
if (effectiveHdr) {
|
|
108747
|
+
log.info(
|
|
108748
|
+
`[Render] HDR source detected \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
|
|
108749
|
+
);
|
|
108750
|
+
}
|
|
107908
108751
|
const stage3Start = Date.now();
|
|
107909
108752
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
107910
108753
|
const audioOutputPath = join15(workDir, "audio.aac");
|
|
@@ -107932,7 +108775,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107932
108775
|
fileServer = await createFileServer2({
|
|
107933
108776
|
projectDir,
|
|
107934
108777
|
compiledDir: join15(workDir, "compiled"),
|
|
107935
|
-
port: 0
|
|
108778
|
+
port: 0,
|
|
108779
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
107936
108780
|
});
|
|
107937
108781
|
assertNotAborted();
|
|
107938
108782
|
}
|
|
@@ -107949,218 +108793,398 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107949
108793
|
const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
107950
108794
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
107951
108795
|
const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
|
|
107952
|
-
const
|
|
107953
|
-
const
|
|
107954
|
-
const
|
|
107955
|
-
const baseEncoderOpts = {
|
|
107956
|
-
fps: job.config.fps,
|
|
107957
|
-
width,
|
|
107958
|
-
height,
|
|
107959
|
-
codec: preset.codec,
|
|
107960
|
-
preset: preset.preset,
|
|
107961
|
-
quality: effectiveQuality,
|
|
107962
|
-
bitrate: effectiveBitrate,
|
|
107963
|
-
pixelFormat: preset.pixelFormat,
|
|
107964
|
-
useGpu: job.config.useGpu
|
|
107965
|
-
};
|
|
108796
|
+
const hasHdrVideo = effectiveHdr && composition.videos.length > 0 && frameLookup;
|
|
108797
|
+
const encoderHdr = hasHdrVideo ? effectiveHdr : void 0;
|
|
108798
|
+
const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
|
|
107966
108799
|
job.framesRendered = 0;
|
|
107967
|
-
|
|
107968
|
-
|
|
107969
|
-
|
|
108800
|
+
if (hasHdrVideo) {
|
|
108801
|
+
log.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
|
|
108802
|
+
const hdrVideoIds = composition.videos.filter((v) => nativeHdrVideoIds.has(v.id)).map((v) => v.id);
|
|
108803
|
+
const hdrVideoSrcPaths = /* @__PURE__ */ new Map();
|
|
108804
|
+
for (const v of composition.videos) {
|
|
108805
|
+
if (!hdrVideoIds.includes(v.id)) continue;
|
|
108806
|
+
let srcPath = v.src;
|
|
108807
|
+
if (!srcPath.startsWith("/")) {
|
|
108808
|
+
const fromCompiled = join15(compiledDir, srcPath);
|
|
108809
|
+
srcPath = existsSync15(fromCompiled) ? fromCompiled : join15(projectDir, srcPath);
|
|
108810
|
+
}
|
|
108811
|
+
hdrVideoSrcPaths.set(v.id, srcPath);
|
|
108812
|
+
}
|
|
108813
|
+
const domSession = await createCaptureSession(
|
|
108814
|
+
fileServer.url,
|
|
108815
|
+
framesDir,
|
|
108816
|
+
captureOptions,
|
|
108817
|
+
createVideoFrameInjector(frameLookup),
|
|
108818
|
+
cfg
|
|
108819
|
+
);
|
|
108820
|
+
await initializeSession(domSession);
|
|
108821
|
+
assertNotAborted();
|
|
108822
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
108823
|
+
await initTransparentBackground(domSession.page);
|
|
108824
|
+
const hdrEncoder = await spawnStreamingEncoder(
|
|
107970
108825
|
videoOnlyPath,
|
|
107971
108826
|
{
|
|
107972
|
-
|
|
107973
|
-
|
|
108827
|
+
fps: job.config.fps,
|
|
108828
|
+
width,
|
|
108829
|
+
height,
|
|
108830
|
+
codec: preset.codec,
|
|
108831
|
+
preset: preset.preset,
|
|
108832
|
+
quality: preset.quality,
|
|
108833
|
+
pixelFormat: preset.pixelFormat,
|
|
108834
|
+
hdr: preset.hdr,
|
|
108835
|
+
rawInputFormat: "rgb48le"
|
|
107974
108836
|
},
|
|
107975
|
-
abortSignal
|
|
108837
|
+
abortSignal,
|
|
108838
|
+
{ ffmpegStreamingTimeout: 36e5 }
|
|
107976
108839
|
);
|
|
107977
108840
|
assertNotAborted();
|
|
107978
|
-
|
|
107979
|
-
|
|
107980
|
-
const
|
|
107981
|
-
|
|
107982
|
-
|
|
107983
|
-
const
|
|
107984
|
-
|
|
107985
|
-
|
|
107986
|
-
|
|
107987
|
-
|
|
107988
|
-
|
|
107989
|
-
|
|
107990
|
-
|
|
107991
|
-
|
|
107992
|
-
|
|
107993
|
-
|
|
107994
|
-
|
|
107995
|
-
|
|
107996
|
-
|
|
107997
|
-
|
|
107998
|
-
|
|
107999
|
-
|
|
108000
|
-
|
|
108001
|
-
|
|
108002
|
-
|
|
108003
|
-
|
|
108004
|
-
|
|
108005
|
-
|
|
108006
|
-
|
|
108841
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
108842
|
+
const hdrFrameDirs = /* @__PURE__ */ new Map();
|
|
108843
|
+
for (const [videoId, srcPath] of hdrVideoSrcPaths) {
|
|
108844
|
+
const video = composition.videos.find((v) => v.id === videoId);
|
|
108845
|
+
if (!video) continue;
|
|
108846
|
+
const frameDir = join15(framesDir, `hdr_${videoId}`);
|
|
108847
|
+
mkdirSync10(frameDir, { recursive: true });
|
|
108848
|
+
const duration = video.end - video.start;
|
|
108849
|
+
try {
|
|
108850
|
+
execSync2(
|
|
108851
|
+
`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")}"`,
|
|
108852
|
+
{ maxBuffer: 1024 * 1024, stdio: ["pipe", "pipe", "pipe"] }
|
|
108853
|
+
);
|
|
108854
|
+
} catch (err) {
|
|
108855
|
+
log.warn("HDR frame pre-extraction failed; loop will fill with black", {
|
|
108856
|
+
videoId,
|
|
108857
|
+
srcPath,
|
|
108858
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108859
|
+
});
|
|
108860
|
+
}
|
|
108861
|
+
hdrFrameDirs.set(videoId, frameDir);
|
|
108862
|
+
}
|
|
108863
|
+
assertNotAborted();
|
|
108864
|
+
try {
|
|
108865
|
+
const beforeCaptureHook = domSession.onBeforeCapture;
|
|
108866
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
108867
|
+
assertNotAborted();
|
|
108868
|
+
const time = i / job.config.fps;
|
|
108869
|
+
await domSession.page.evaluate((t) => {
|
|
108870
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
|
|
108871
|
+
}, time);
|
|
108872
|
+
if (beforeCaptureHook) {
|
|
108873
|
+
await beforeCaptureHook(domSession.page, time);
|
|
108874
|
+
}
|
|
108875
|
+
const stackingInfo = await queryElementStacking(domSession.page, nativeHdrVideoIds);
|
|
108876
|
+
const layers = groupIntoLayers(stackingInfo);
|
|
108877
|
+
if (i % 30 === 0) {
|
|
108878
|
+
const hdrEl = stackingInfo.find((e) => e.isHdr);
|
|
108879
|
+
const hdrInLayers = layers.some((l) => l.type === "hdr");
|
|
108880
|
+
log.debug("[Render] HDR layer composite frame", {
|
|
108881
|
+
frame: i,
|
|
108882
|
+
time: time.toFixed(2),
|
|
108883
|
+
hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
|
|
108884
|
+
hdrLayerPresent: hdrInLayers,
|
|
108885
|
+
layerCount: layers.length
|
|
108886
|
+
});
|
|
108887
|
+
}
|
|
108888
|
+
const canvas = Buffer.alloc(width * height * 6);
|
|
108889
|
+
for (const layer of layers) {
|
|
108890
|
+
if (layer.type === "hdr") {
|
|
108891
|
+
const el = layer.element;
|
|
108892
|
+
const frameDir = hdrFrameDirs.get(el.id);
|
|
108893
|
+
const video = composition.videos.find((v) => v.id === el.id);
|
|
108894
|
+
if (!frameDir || !video) continue;
|
|
108895
|
+
const videoFrameIndex = Math.round((time - video.start) * job.config.fps) + 1;
|
|
108896
|
+
const maxIndex = getMaxFrameIndex(frameDir);
|
|
108897
|
+
const inBounds = videoFrameIndex >= 1 && (maxIndex === 0 || videoFrameIndex <= maxIndex);
|
|
108898
|
+
const framePath = inBounds ? join15(frameDir, `frame_${String(videoFrameIndex).padStart(4, "0")}.png`) : null;
|
|
108899
|
+
if (framePath !== null && existsSync15(framePath)) {
|
|
108900
|
+
try {
|
|
108901
|
+
const hdrRgb = decodePngToRgb48le(readFileSync9(framePath)).data;
|
|
108902
|
+
blitRgb48leRegion(
|
|
108903
|
+
canvas,
|
|
108904
|
+
hdrRgb,
|
|
108905
|
+
el.x,
|
|
108906
|
+
el.y,
|
|
108907
|
+
el.width,
|
|
108908
|
+
el.height,
|
|
108909
|
+
width,
|
|
108910
|
+
height,
|
|
108911
|
+
el.opacity < 0.999 ? el.opacity : void 0
|
|
108912
|
+
);
|
|
108913
|
+
} catch (err) {
|
|
108914
|
+
log.warn("HDR layer decode/blit failed; skipping layer for frame", {
|
|
108915
|
+
frameIndex: i,
|
|
108916
|
+
videoId: el.id,
|
|
108917
|
+
framePath,
|
|
108918
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108919
|
+
});
|
|
108920
|
+
}
|
|
108921
|
+
}
|
|
108922
|
+
} else {
|
|
108923
|
+
const allElementIds = stackingInfo.map((e) => e.id);
|
|
108924
|
+
const layerIds = new Set(layer.elementIds);
|
|
108925
|
+
const hideIds = allElementIds.filter(
|
|
108926
|
+
(id) => !layerIds.has(id) || nativeHdrVideoIds.has(id)
|
|
108007
108927
|
);
|
|
108928
|
+
await hideVideoElements(domSession.page, hideIds);
|
|
108929
|
+
const domPng = await captureAlphaPng(domSession.page, width, height);
|
|
108930
|
+
await showVideoElements(domSession.page, hideIds);
|
|
108931
|
+
await domSession.page.evaluate((t) => {
|
|
108932
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
|
|
108933
|
+
}, time);
|
|
108934
|
+
try {
|
|
108935
|
+
const { data: domRgba } = decodePng(domPng);
|
|
108936
|
+
const hdrTransfer = effectiveHdr ? effectiveHdr.transfer : "hlg";
|
|
108937
|
+
blitRgba8OverRgb48le(domRgba, canvas, width, height, hdrTransfer);
|
|
108938
|
+
} catch (err) {
|
|
108939
|
+
log.warn("DOM layer decode/blit failed; skipping overlay for frame", {
|
|
108940
|
+
frameIndex: i,
|
|
108941
|
+
layerIds: layer.elementIds,
|
|
108942
|
+
error: err instanceof Error ? err.message : String(err)
|
|
108943
|
+
});
|
|
108944
|
+
}
|
|
108008
108945
|
}
|
|
108009
|
-
},
|
|
108010
|
-
onFrameBuffer,
|
|
108011
|
-
cfg
|
|
108012
|
-
);
|
|
108013
|
-
if (probeSession) {
|
|
108014
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108015
|
-
await closeCaptureSession(probeSession);
|
|
108016
|
-
probeSession = null;
|
|
108017
|
-
}
|
|
108018
|
-
} else {
|
|
108019
|
-
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
108020
|
-
const session = probeSession ?? await createCaptureSession(
|
|
108021
|
-
fileServer.url,
|
|
108022
|
-
framesDir,
|
|
108023
|
-
captureOptions,
|
|
108024
|
-
videoInjector,
|
|
108025
|
-
cfg
|
|
108026
|
-
);
|
|
108027
|
-
if (probeSession) {
|
|
108028
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108029
|
-
probeSession = null;
|
|
108030
|
-
}
|
|
108031
|
-
try {
|
|
108032
|
-
if (!session.isInitialized) {
|
|
108033
|
-
await initializeSession(session);
|
|
108034
108946
|
}
|
|
108035
|
-
|
|
108036
|
-
|
|
108037
|
-
|
|
108038
|
-
assertNotAborted();
|
|
108039
|
-
const time = i / job.config.fps;
|
|
108040
|
-
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
108041
|
-
await reorderBuffer.waitForFrame(i);
|
|
108042
|
-
currentEncoder.writeFrame(buffer);
|
|
108043
|
-
reorderBuffer.advanceTo(i + 1);
|
|
108044
|
-
job.framesRendered = i + 1;
|
|
108947
|
+
hdrEncoder.writeFrame(canvas);
|
|
108948
|
+
job.framesRendered = i + 1;
|
|
108949
|
+
if ((i + 1) % 10 === 0 || i + 1 === job.totalFrames) {
|
|
108045
108950
|
const frameProgress = (i + 1) / job.totalFrames;
|
|
108046
|
-
const progress = 25 + frameProgress * 55;
|
|
108047
108951
|
updateJobStatus(
|
|
108048
108952
|
job,
|
|
108049
108953
|
"rendering",
|
|
108050
|
-
`
|
|
108051
|
-
Math.round(
|
|
108954
|
+
`HDR composite frame ${i + 1}/${job.totalFrames}`,
|
|
108955
|
+
Math.round(25 + frameProgress * 55),
|
|
108052
108956
|
onProgress
|
|
108053
108957
|
);
|
|
108054
108958
|
}
|
|
108055
|
-
} finally {
|
|
108056
|
-
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108057
|
-
await closeCaptureSession(session);
|
|
108058
108959
|
}
|
|
108960
|
+
} finally {
|
|
108961
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
108962
|
+
await closeCaptureSession(domSession);
|
|
108059
108963
|
}
|
|
108060
|
-
const
|
|
108964
|
+
const hdrEncodeResult = await hdrEncoder.close();
|
|
108061
108965
|
assertNotAborted();
|
|
108062
|
-
if (!
|
|
108063
|
-
throw new Error(`
|
|
108966
|
+
if (!hdrEncodeResult.success) {
|
|
108967
|
+
throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
|
|
108064
108968
|
}
|
|
108065
108969
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
108066
|
-
perfStages.encodeMs =
|
|
108970
|
+
perfStages.encodeMs = hdrEncodeResult.durationMs;
|
|
108067
108971
|
} else {
|
|
108068
|
-
|
|
108069
|
-
|
|
108070
|
-
await
|
|
108071
|
-
|
|
108072
|
-
|
|
108073
|
-
|
|
108074
|
-
|
|
108075
|
-
|
|
108076
|
-
|
|
108077
|
-
|
|
108078
|
-
|
|
108079
|
-
|
|
108080
|
-
|
|
108081
|
-
|
|
108972
|
+
let streamingEncoder = null;
|
|
108973
|
+
if (enableStreamingEncode) {
|
|
108974
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
108975
|
+
videoOnlyPath,
|
|
108976
|
+
{
|
|
108977
|
+
fps: job.config.fps,
|
|
108978
|
+
width,
|
|
108979
|
+
height,
|
|
108980
|
+
codec: preset.codec,
|
|
108981
|
+
preset: preset.preset,
|
|
108982
|
+
quality: preset.quality,
|
|
108983
|
+
pixelFormat: preset.pixelFormat,
|
|
108984
|
+
useGpu: job.config.useGpu,
|
|
108985
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
108986
|
+
hdr: preset.hdr
|
|
108987
|
+
},
|
|
108988
|
+
abortSignal
|
|
108989
|
+
);
|
|
108990
|
+
assertNotAborted();
|
|
108991
|
+
}
|
|
108992
|
+
if (enableStreamingEncode && streamingEncoder) {
|
|
108993
|
+
const reorderBuffer = createFrameReorderBuffer(0, job.totalFrames);
|
|
108994
|
+
const currentEncoder = streamingEncoder;
|
|
108995
|
+
if (workerCount > 1) {
|
|
108996
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
108997
|
+
const onFrameBuffer = async (frameIndex, buffer) => {
|
|
108998
|
+
await reorderBuffer.waitForFrame(frameIndex);
|
|
108999
|
+
currentEncoder.writeFrame(buffer);
|
|
109000
|
+
reorderBuffer.advanceTo(frameIndex + 1);
|
|
109001
|
+
};
|
|
109002
|
+
await executeParallelCapture(
|
|
109003
|
+
fileServer.url,
|
|
109004
|
+
workDir,
|
|
109005
|
+
tasks,
|
|
109006
|
+
captureOptions,
|
|
109007
|
+
() => createVideoFrameInjector(frameLookup),
|
|
109008
|
+
abortSignal,
|
|
109009
|
+
(progress) => {
|
|
109010
|
+
job.framesRendered = progress.capturedFrames;
|
|
109011
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
109012
|
+
const progressPct = 25 + frameProgress * 55;
|
|
109013
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
109014
|
+
updateJobStatus(
|
|
109015
|
+
job,
|
|
109016
|
+
"rendering",
|
|
109017
|
+
`Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
109018
|
+
Math.round(progressPct),
|
|
109019
|
+
onProgress
|
|
109020
|
+
);
|
|
109021
|
+
}
|
|
109022
|
+
},
|
|
109023
|
+
onFrameBuffer,
|
|
109024
|
+
cfg
|
|
109025
|
+
);
|
|
109026
|
+
if (probeSession) {
|
|
109027
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
109028
|
+
await closeCaptureSession(probeSession);
|
|
109029
|
+
probeSession = null;
|
|
109030
|
+
}
|
|
109031
|
+
} else {
|
|
109032
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
109033
|
+
const session = probeSession ?? await createCaptureSession(
|
|
109034
|
+
fileServer.url,
|
|
109035
|
+
framesDir,
|
|
109036
|
+
captureOptions,
|
|
109037
|
+
videoInjector,
|
|
109038
|
+
cfg
|
|
109039
|
+
);
|
|
109040
|
+
if (probeSession) {
|
|
109041
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
109042
|
+
probeSession = null;
|
|
109043
|
+
}
|
|
109044
|
+
try {
|
|
109045
|
+
if (!session.isInitialized) {
|
|
109046
|
+
await initializeSession(session);
|
|
109047
|
+
}
|
|
109048
|
+
assertNotAborted();
|
|
109049
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
109050
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
109051
|
+
assertNotAborted();
|
|
109052
|
+
const time = i / job.config.fps;
|
|
109053
|
+
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
109054
|
+
await reorderBuffer.waitForFrame(i);
|
|
109055
|
+
currentEncoder.writeFrame(buffer);
|
|
109056
|
+
reorderBuffer.advanceTo(i + 1);
|
|
109057
|
+
job.framesRendered = i + 1;
|
|
109058
|
+
const frameProgress = (i + 1) / job.totalFrames;
|
|
109059
|
+
const progress = 25 + frameProgress * 55;
|
|
108082
109060
|
updateJobStatus(
|
|
108083
109061
|
job,
|
|
108084
109062
|
"rendering",
|
|
108085
|
-
`
|
|
108086
|
-
Math.round(
|
|
109063
|
+
`Streaming frame ${i + 1}/${job.totalFrames}`,
|
|
109064
|
+
Math.round(progress),
|
|
108087
109065
|
onProgress
|
|
108088
109066
|
);
|
|
108089
109067
|
}
|
|
108090
|
-
}
|
|
108091
|
-
|
|
108092
|
-
|
|
108093
|
-
|
|
108094
|
-
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
108095
|
-
if (probeSession) {
|
|
108096
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108097
|
-
await closeCaptureSession(probeSession);
|
|
108098
|
-
probeSession = null;
|
|
109068
|
+
} finally {
|
|
109069
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
109070
|
+
await closeCaptureSession(session);
|
|
109071
|
+
}
|
|
108099
109072
|
}
|
|
108100
|
-
|
|
108101
|
-
|
|
108102
|
-
|
|
108103
|
-
|
|
108104
|
-
framesDir,
|
|
108105
|
-
captureOptions,
|
|
108106
|
-
videoInjector,
|
|
108107
|
-
cfg
|
|
108108
|
-
);
|
|
108109
|
-
if (probeSession) {
|
|
108110
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108111
|
-
probeSession = null;
|
|
109073
|
+
const encodeResult = await currentEncoder.close();
|
|
109074
|
+
assertNotAborted();
|
|
109075
|
+
if (!encodeResult.success) {
|
|
109076
|
+
throw new Error(`Streaming encode failed: ${encodeResult.error}`);
|
|
108112
109077
|
}
|
|
108113
|
-
|
|
108114
|
-
|
|
108115
|
-
|
|
109078
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
109079
|
+
perfStages.encodeMs = encodeResult.durationMs;
|
|
109080
|
+
} else {
|
|
109081
|
+
if (workerCount > 1) {
|
|
109082
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
109083
|
+
await executeParallelCapture(
|
|
109084
|
+
fileServer.url,
|
|
109085
|
+
workDir,
|
|
109086
|
+
tasks,
|
|
109087
|
+
captureOptions,
|
|
109088
|
+
() => createVideoFrameInjector(frameLookup),
|
|
109089
|
+
abortSignal,
|
|
109090
|
+
(progress) => {
|
|
109091
|
+
job.framesRendered = progress.capturedFrames;
|
|
109092
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
109093
|
+
const progressPct = 25 + frameProgress * 45;
|
|
109094
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
109095
|
+
updateJobStatus(
|
|
109096
|
+
job,
|
|
109097
|
+
"rendering",
|
|
109098
|
+
`Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
109099
|
+
Math.round(progressPct),
|
|
109100
|
+
onProgress
|
|
109101
|
+
);
|
|
109102
|
+
}
|
|
109103
|
+
},
|
|
109104
|
+
void 0,
|
|
109105
|
+
cfg
|
|
109106
|
+
);
|
|
109107
|
+
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
109108
|
+
if (probeSession) {
|
|
109109
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
109110
|
+
await closeCaptureSession(probeSession);
|
|
109111
|
+
probeSession = null;
|
|
108116
109112
|
}
|
|
108117
|
-
|
|
108118
|
-
|
|
108119
|
-
|
|
108120
|
-
|
|
108121
|
-
|
|
108122
|
-
|
|
108123
|
-
|
|
108124
|
-
|
|
108125
|
-
|
|
108126
|
-
|
|
108127
|
-
|
|
108128
|
-
|
|
108129
|
-
`Capturing frame ${i + 1}/${job.totalFrames}`,
|
|
108130
|
-
Math.round(progress),
|
|
108131
|
-
onProgress
|
|
108132
|
-
);
|
|
109113
|
+
} else {
|
|
109114
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
109115
|
+
const session = probeSession ?? await createCaptureSession(
|
|
109116
|
+
fileServer.url,
|
|
109117
|
+
framesDir,
|
|
109118
|
+
captureOptions,
|
|
109119
|
+
videoInjector,
|
|
109120
|
+
cfg
|
|
109121
|
+
);
|
|
109122
|
+
if (probeSession) {
|
|
109123
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
109124
|
+
probeSession = null;
|
|
108133
109125
|
}
|
|
108134
|
-
|
|
108135
|
-
|
|
108136
|
-
|
|
109126
|
+
try {
|
|
109127
|
+
if (!session.isInitialized) {
|
|
109128
|
+
await initializeSession(session);
|
|
109129
|
+
}
|
|
109130
|
+
assertNotAborted();
|
|
109131
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
109132
|
+
for (let i = 0; i < job.totalFrames; i++) {
|
|
109133
|
+
assertNotAborted();
|
|
109134
|
+
const time = i / job.config.fps;
|
|
109135
|
+
await captureFrame(session, i, time);
|
|
109136
|
+
job.framesRendered = i + 1;
|
|
109137
|
+
const frameProgress = (i + 1) / job.totalFrames;
|
|
109138
|
+
const progress = 25 + frameProgress * 45;
|
|
109139
|
+
updateJobStatus(
|
|
109140
|
+
job,
|
|
109141
|
+
"rendering",
|
|
109142
|
+
`Capturing frame ${i + 1}/${job.totalFrames}`,
|
|
109143
|
+
Math.round(progress),
|
|
109144
|
+
onProgress
|
|
109145
|
+
);
|
|
109146
|
+
}
|
|
109147
|
+
} finally {
|
|
109148
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
109149
|
+
await closeCaptureSession(session);
|
|
109150
|
+
}
|
|
109151
|
+
}
|
|
109152
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
109153
|
+
const stage5Start = Date.now();
|
|
109154
|
+
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
109155
|
+
const frameExt = needsAlpha ? "png" : "jpg";
|
|
109156
|
+
const framePattern = `frame_%06d.${frameExt}`;
|
|
109157
|
+
const encoderOpts = {
|
|
109158
|
+
fps: job.config.fps,
|
|
109159
|
+
width,
|
|
109160
|
+
height,
|
|
109161
|
+
codec: preset.codec,
|
|
109162
|
+
preset: preset.preset,
|
|
109163
|
+
quality: preset.quality,
|
|
109164
|
+
pixelFormat: preset.pixelFormat,
|
|
109165
|
+
useGpu: job.config.useGpu,
|
|
109166
|
+
hdr: preset.hdr
|
|
109167
|
+
};
|
|
109168
|
+
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
109169
|
+
framesDir,
|
|
109170
|
+
framePattern,
|
|
109171
|
+
videoOnlyPath,
|
|
109172
|
+
encoderOpts,
|
|
109173
|
+
chunkedEncodeSize,
|
|
109174
|
+
abortSignal
|
|
109175
|
+
) : await encodeFramesFromDir(
|
|
109176
|
+
framesDir,
|
|
109177
|
+
framePattern,
|
|
109178
|
+
videoOnlyPath,
|
|
109179
|
+
encoderOpts,
|
|
109180
|
+
abortSignal
|
|
109181
|
+
);
|
|
109182
|
+
assertNotAborted();
|
|
109183
|
+
if (!encodeResult.success) {
|
|
109184
|
+
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
108137
109185
|
}
|
|
109186
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
108138
109187
|
}
|
|
108139
|
-
perfStages.captureMs = Date.now() - stage4Start;
|
|
108140
|
-
const stage5Start = Date.now();
|
|
108141
|
-
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
108142
|
-
const frameExt = needsAlpha ? "png" : "jpg";
|
|
108143
|
-
const framePattern = `frame_%06d.${frameExt}`;
|
|
108144
|
-
const encoderOpts = baseEncoderOpts;
|
|
108145
|
-
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
108146
|
-
framesDir,
|
|
108147
|
-
framePattern,
|
|
108148
|
-
videoOnlyPath,
|
|
108149
|
-
encoderOpts,
|
|
108150
|
-
chunkedEncodeSize,
|
|
108151
|
-
abortSignal
|
|
108152
|
-
) : await encodeFramesFromDir(
|
|
108153
|
-
framesDir,
|
|
108154
|
-
framePattern,
|
|
108155
|
-
videoOnlyPath,
|
|
108156
|
-
encoderOpts,
|
|
108157
|
-
abortSignal
|
|
108158
|
-
);
|
|
108159
|
-
assertNotAborted();
|
|
108160
|
-
if (!encodeResult.success) {
|
|
108161
|
-
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
108162
|
-
}
|
|
108163
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
108164
109188
|
}
|
|
108165
109189
|
if (probeSession !== null) {
|
|
108166
109190
|
const remainingProbeSession = probeSession;
|