@hyperframes/producer 0.4.6 → 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/index.js +1084 -286
- package/dist/index.js.map +4 -4
- package/dist/public-server.js +1084 -286
- package/dist/public-server.js.map +4 -4
- package/dist/services/fileServer.d.ts +17 -2
- package/dist/services/fileServer.d.ts.map +1 -1
- package/dist/services/renderOrchestrator.d.ts +2 -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);
|
|
@@ -104241,7 +104268,7 @@ var ENCODER_PRESETS = {
|
|
|
104241
104268
|
standard: { preset: "medium", quality: 18, codec: "h264" },
|
|
104242
104269
|
high: { preset: "slow", quality: 15, codec: "h264" }
|
|
104243
104270
|
};
|
|
104244
|
-
function getEncoderPreset(quality, format3 = "mp4") {
|
|
104271
|
+
function getEncoderPreset(quality, format3 = "mp4", hdr) {
|
|
104245
104272
|
const base = ENCODER_PRESETS[quality];
|
|
104246
104273
|
if (format3 === "webm") {
|
|
104247
104274
|
return {
|
|
@@ -104259,6 +104286,15 @@ function getEncoderPreset(quality, format3 = "mp4") {
|
|
|
104259
104286
|
pixelFormat: "yuva444p10le"
|
|
104260
104287
|
};
|
|
104261
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
|
+
}
|
|
104262
104298
|
return { ...base, pixelFormat: "yuv420p" };
|
|
104263
104299
|
}
|
|
104264
104300
|
function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
@@ -104316,6 +104352,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
104316
104352
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
104317
104353
|
}
|
|
104318
104354
|
}
|
|
104355
|
+
if (codec === "h265") {
|
|
104356
|
+
args.push("-tag:v", "hvc1");
|
|
104357
|
+
}
|
|
104319
104358
|
} else if (codec === "vp9") {
|
|
104320
104359
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
104321
104360
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
@@ -104622,31 +104661,79 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
|
|
|
104622
104661
|
import { spawn as spawn6 } from "child_process";
|
|
104623
104662
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync4 } from "fs";
|
|
104624
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
|
|
104625
104701
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
104626
|
-
let
|
|
104627
|
-
|
|
104628
|
-
const
|
|
104629
|
-
|
|
104630
|
-
|
|
104631
|
-
|
|
104632
|
-
|
|
104633
|
-
|
|
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);
|
|
104634
104710
|
}
|
|
104635
104711
|
};
|
|
104636
|
-
|
|
104637
|
-
|
|
104638
|
-
|
|
104639
|
-
|
|
104640
|
-
|
|
104641
|
-
|
|
104642
|
-
|
|
104643
|
-
|
|
104644
|
-
|
|
104645
|
-
|
|
104646
|
-
|
|
104647
|
-
|
|
104648
|
-
|
|
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();
|
|
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);
|
|
104649
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 };
|
|
104650
104737
|
}
|
|
104651
104738
|
function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
104652
104739
|
const {
|
|
@@ -104659,19 +104746,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104659
104746
|
useGpu = false,
|
|
104660
104747
|
imageFormat = "jpeg"
|
|
104661
104748
|
} = options;
|
|
104662
|
-
const
|
|
104663
|
-
|
|
104664
|
-
|
|
104665
|
-
"
|
|
104666
|
-
|
|
104667
|
-
|
|
104668
|
-
|
|
104669
|
-
|
|
104670
|
-
|
|
104671
|
-
|
|
104672
|
-
|
|
104673
|
-
|
|
104674
|
-
|
|
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));
|
|
104675
104779
|
const shouldUseGpu = useGpu && gpuEncoder !== null;
|
|
104676
104780
|
if (codec === "h264" || codec === "h265") {
|
|
104677
104781
|
if (shouldUseGpu) {
|
|
@@ -104709,12 +104813,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104709
104813
|
if (bitrate) args.push("-b:v", bitrate);
|
|
104710
104814
|
else args.push("-crf", String(quality));
|
|
104711
104815
|
const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
|
|
104712
|
-
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";
|
|
104713
104817
|
if (preset === "ultrafast") {
|
|
104714
104818
|
args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
|
|
104715
104819
|
} else {
|
|
104716
104820
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
104717
104821
|
}
|
|
104822
|
+
if (codec === "h265") {
|
|
104823
|
+
args.push("-tag:v", "hvc1");
|
|
104824
|
+
}
|
|
104718
104825
|
}
|
|
104719
104826
|
} else if (codec === "vp9") {
|
|
104720
104827
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
@@ -104730,17 +104837,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
104730
104837
|
return [...args, "-y", outputPath];
|
|
104731
104838
|
}
|
|
104732
104839
|
if (codec === "h264" || codec === "h265") {
|
|
104733
|
-
|
|
104734
|
-
|
|
104735
|
-
|
|
104736
|
-
|
|
104737
|
-
|
|
104738
|
-
|
|
104739
|
-
|
|
104740
|
-
|
|
104741
|
-
|
|
104742
|
-
|
|
104743
|
-
|
|
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") {
|
|
104744
104865
|
const vfIdx = args.indexOf("-vf");
|
|
104745
104866
|
if (vfIdx !== -1) {
|
|
104746
104867
|
args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
|
|
@@ -104810,14 +104931,16 @@ Process error: ${err.message}`;
|
|
|
104810
104931
|
if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
|
|
104811
104932
|
return false;
|
|
104812
104933
|
}
|
|
104813
|
-
|
|
104934
|
+
const copy = Buffer.from(buffer);
|
|
104935
|
+
return ffmpeg.stdin.write(copy);
|
|
104814
104936
|
},
|
|
104815
104937
|
close: async () => {
|
|
104816
104938
|
clearTimeout(timer2);
|
|
104817
104939
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
104818
|
-
|
|
104940
|
+
const stdin = ffmpeg.stdin;
|
|
104941
|
+
if (stdin && !stdin.destroyed) {
|
|
104819
104942
|
await new Promise((resolve13) => {
|
|
104820
|
-
|
|
104943
|
+
stdin.end(() => resolve13());
|
|
104821
104944
|
});
|
|
104822
104945
|
}
|
|
104823
104946
|
await exitPromise;
|
|
@@ -104921,6 +105044,10 @@ async function extractVideoMetadata(filePath) {
|
|
|
104921
105044
|
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
104922
105045
|
const fps = avgFps || rFps;
|
|
104923
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);
|
|
104924
105051
|
return {
|
|
104925
105052
|
durationSeconds: output2.format.duration ? parseFloat(output2.format.duration) : 0,
|
|
104926
105053
|
width: videoStream.width || 0,
|
|
@@ -104928,7 +105055,8 @@ async function extractVideoMetadata(filePath) {
|
|
|
104928
105055
|
fps,
|
|
104929
105056
|
videoCodec: videoStream.codec_name || "unknown",
|
|
104930
105057
|
hasAudio: output2.streams.some((s) => s.codec_type === "audio"),
|
|
104931
|
-
isVFR
|
|
105058
|
+
isVFR,
|
|
105059
|
+
colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
|
|
104932
105060
|
};
|
|
104933
105061
|
})();
|
|
104934
105062
|
videoMetadataCache.set(filePath, probePromise);
|
|
@@ -105136,18 +105264,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
105136
105264
|
const metadata = await extractVideoMetadata(videoPath);
|
|
105137
105265
|
const framePattern = `frame_%05d.${format3}`;
|
|
105138
105266
|
const outputPattern = join8(videoOutputDir, framePattern);
|
|
105139
|
-
const
|
|
105140
|
-
|
|
105141
|
-
|
|
105142
|
-
|
|
105143
|
-
|
|
105144
|
-
|
|
105145
|
-
|
|
105146
|
-
|
|
105147
|
-
|
|
105148
|
-
"
|
|
105149
|
-
|
|
105150
|
-
|
|
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");
|
|
105151
105281
|
if (format3 === "png") args.push("-compression_level", "6");
|
|
105152
105282
|
args.push("-y", outputPattern);
|
|
105153
105283
|
return new Promise((resolve13, reject) => {
|
|
@@ -105207,30 +105337,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
105207
105337
|
});
|
|
105208
105338
|
});
|
|
105209
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
|
+
}
|
|
105210
105371
|
async function extractAllVideoFrames(videos, baseDir, options, signal, config2, compiledDir) {
|
|
105211
105372
|
const startTime = Date.now();
|
|
105212
105373
|
const extracted = [];
|
|
105213
105374
|
const errors = [];
|
|
105214
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
|
+
}
|
|
105215
105428
|
const results = await Promise.all(
|
|
105216
|
-
|
|
105429
|
+
resolvedVideos.map(async ({ video, videoPath }) => {
|
|
105217
105430
|
if (signal?.aborted) {
|
|
105218
105431
|
throw new Error("Video frame extraction cancelled");
|
|
105219
105432
|
}
|
|
105220
105433
|
try {
|
|
105221
|
-
let videoPath = video.src;
|
|
105222
|
-
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
105223
|
-
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
105224
|
-
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
105225
|
-
}
|
|
105226
|
-
if (isHttpUrl(videoPath)) {
|
|
105227
|
-
const downloadDir = join8(options.outputDir, "_downloads");
|
|
105228
|
-
mkdirSync5(downloadDir, { recursive: true });
|
|
105229
|
-
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
105230
|
-
}
|
|
105231
|
-
if (!existsSync8(videoPath)) {
|
|
105232
|
-
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
105233
|
-
}
|
|
105234
105434
|
let videoDuration = video.end - video.start;
|
|
105235
105435
|
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
|
|
105236
105436
|
const metadata = await extractVideoMetadata(videoPath);
|
|
@@ -105465,6 +105665,74 @@ function createVideoFrameInjector(frameLookup, config2) {
|
|
|
105465
105665
|
}
|
|
105466
105666
|
};
|
|
105467
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
|
+
}
|
|
105468
105736
|
|
|
105469
105737
|
// ../engine/src/services/audioMixer.ts
|
|
105470
105738
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
@@ -105951,6 +106219,292 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
|
|
|
105951
106219
|
return totalFrames;
|
|
105952
106220
|
}
|
|
105953
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
|
+
|
|
105954
106508
|
// src/services/renderOrchestrator.ts
|
|
105955
106509
|
import { join as join15, dirname as dirname10, resolve as resolve10 } from "path";
|
|
105956
106510
|
import { randomUUID } from "crypto";
|
|
@@ -106271,6 +106825,10 @@ var RENDER_MODE_SCRIPT = `(function() {
|
|
|
106271
106825
|
}
|
|
106272
106826
|
waitForPlayer();
|
|
106273
106827
|
})();`;
|
|
106828
|
+
var HF_EARLY_STUB = `(function() {
|
|
106829
|
+
if (typeof window === "undefined") return;
|
|
106830
|
+
if (!window.__hf) window.__hf = {};
|
|
106831
|
+
})();`;
|
|
106274
106832
|
var HF_BRIDGE_SCRIPT = `(function() {
|
|
106275
106833
|
var __realSetInterval =
|
|
106276
106834
|
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
|
|
@@ -106316,20 +106874,24 @@ var HF_BRIDGE_SCRIPT = `(function() {
|
|
|
106316
106874
|
if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
|
|
106317
106875
|
return false;
|
|
106318
106876
|
}
|
|
106319
|
-
window.__hf
|
|
106320
|
-
|
|
106877
|
+
var hf = window.__hf || {};
|
|
106878
|
+
Object.defineProperty(hf, "duration", {
|
|
106879
|
+
configurable: true,
|
|
106880
|
+
enumerable: true,
|
|
106881
|
+
get: function() {
|
|
106321
106882
|
var d = p.getDuration();
|
|
106322
106883
|
return d > 0 ? d : getDeclaredDuration();
|
|
106323
106884
|
},
|
|
106324
|
-
|
|
106325
|
-
|
|
106326
|
-
|
|
106327
|
-
|
|
106328
|
-
|
|
106329
|
-
|
|
106330
|
-
|
|
106331
|
-
|
|
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);
|
|
106332
106893
|
};
|
|
106894
|
+
window.__hf = hf;
|
|
106333
106895
|
return true;
|
|
106334
106896
|
}
|
|
106335
106897
|
if (bridge()) return;
|
|
@@ -106411,7 +106973,7 @@ ${headTags}`);
|
|
|
106411
106973
|
}
|
|
106412
106974
|
function createFileServer2(options) {
|
|
106413
106975
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
106414
|
-
const preHeadScripts = options.preHeadScripts ?? [];
|
|
106976
|
+
const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
|
|
106415
106977
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
106416
106978
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
106417
106979
|
const app = new Hono2();
|
|
@@ -107671,6 +108233,24 @@ async function safeCleanup(label, fn, log = defaultLogger) {
|
|
|
107671
108233
|
});
|
|
107672
108234
|
}
|
|
107673
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
|
+
}
|
|
107674
108254
|
var RenderCancelledError = class extends Error {
|
|
107675
108255
|
reason;
|
|
107676
108256
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -108070,7 +108650,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
108070
108650
|
}
|
|
108071
108651
|
}
|
|
108072
108652
|
}
|
|
108073
|
-
} 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
|
+
});
|
|
108074
108657
|
diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
|
|
108075
108658
|
}
|
|
108076
108659
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
@@ -108094,8 +108677,26 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
108094
108677
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
108095
108678
|
let frameLookup = null;
|
|
108096
108679
|
const compiledDir = join15(workDir, "compiled");
|
|
108680
|
+
let extractionResult = null;
|
|
108681
|
+
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
108097
108682
|
if (composition.videos.length > 0) {
|
|
108098
|
-
|
|
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
|
+
}
|
|
108698
|
+
if (composition.videos.length > 0) {
|
|
108699
|
+
extractionResult = await extractAllVideoFrames(
|
|
108099
108700
|
composition.videos,
|
|
108100
108701
|
projectDir,
|
|
108101
108702
|
{ fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
|
|
@@ -108130,6 +108731,23 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
108130
108731
|
} else {
|
|
108131
108732
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
108132
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
|
+
}
|
|
108133
108751
|
const stage3Start = Date.now();
|
|
108134
108752
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
108135
108753
|
const audioOutputPath = join15(workDir, "audio.aac");
|
|
@@ -108175,218 +108793,398 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
108175
108793
|
const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
108176
108794
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
108177
108795
|
const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
|
|
108178
|
-
const
|
|
108179
|
-
const
|
|
108180
|
-
const
|
|
108181
|
-
const baseEncoderOpts = {
|
|
108182
|
-
fps: job.config.fps,
|
|
108183
|
-
width,
|
|
108184
|
-
height,
|
|
108185
|
-
codec: preset.codec,
|
|
108186
|
-
preset: preset.preset,
|
|
108187
|
-
quality: effectiveQuality,
|
|
108188
|
-
bitrate: effectiveBitrate,
|
|
108189
|
-
pixelFormat: preset.pixelFormat,
|
|
108190
|
-
useGpu: job.config.useGpu
|
|
108191
|
-
};
|
|
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);
|
|
108192
108799
|
job.framesRendered = 0;
|
|
108193
|
-
|
|
108194
|
-
|
|
108195
|
-
|
|
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(
|
|
108196
108825
|
videoOnlyPath,
|
|
108197
108826
|
{
|
|
108198
|
-
|
|
108199
|
-
|
|
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"
|
|
108200
108836
|
},
|
|
108201
|
-
abortSignal
|
|
108837
|
+
abortSignal,
|
|
108838
|
+
{ ffmpegStreamingTimeout: 36e5 }
|
|
108202
108839
|
);
|
|
108203
108840
|
assertNotAborted();
|
|
108204
|
-
|
|
108205
|
-
|
|
108206
|
-
const
|
|
108207
|
-
|
|
108208
|
-
|
|
108209
|
-
const
|
|
108210
|
-
|
|
108211
|
-
|
|
108212
|
-
|
|
108213
|
-
|
|
108214
|
-
|
|
108215
|
-
|
|
108216
|
-
|
|
108217
|
-
|
|
108218
|
-
|
|
108219
|
-
|
|
108220
|
-
|
|
108221
|
-
|
|
108222
|
-
|
|
108223
|
-
|
|
108224
|
-
|
|
108225
|
-
|
|
108226
|
-
|
|
108227
|
-
|
|
108228
|
-
|
|
108229
|
-
|
|
108230
|
-
|
|
108231
|
-
|
|
108232
|
-
|
|
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)
|
|
108233
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
|
+
}
|
|
108234
108945
|
}
|
|
108235
|
-
},
|
|
108236
|
-
onFrameBuffer,
|
|
108237
|
-
cfg
|
|
108238
|
-
);
|
|
108239
|
-
if (probeSession) {
|
|
108240
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108241
|
-
await closeCaptureSession(probeSession);
|
|
108242
|
-
probeSession = null;
|
|
108243
|
-
}
|
|
108244
|
-
} else {
|
|
108245
|
-
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
108246
|
-
const session = probeSession ?? await createCaptureSession(
|
|
108247
|
-
fileServer.url,
|
|
108248
|
-
framesDir,
|
|
108249
|
-
captureOptions,
|
|
108250
|
-
videoInjector,
|
|
108251
|
-
cfg
|
|
108252
|
-
);
|
|
108253
|
-
if (probeSession) {
|
|
108254
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108255
|
-
probeSession = null;
|
|
108256
|
-
}
|
|
108257
|
-
try {
|
|
108258
|
-
if (!session.isInitialized) {
|
|
108259
|
-
await initializeSession(session);
|
|
108260
108946
|
}
|
|
108261
|
-
|
|
108262
|
-
|
|
108263
|
-
|
|
108264
|
-
assertNotAborted();
|
|
108265
|
-
const time = i / job.config.fps;
|
|
108266
|
-
const { buffer } = await captureFrameToBuffer(session, i, time);
|
|
108267
|
-
await reorderBuffer.waitForFrame(i);
|
|
108268
|
-
currentEncoder.writeFrame(buffer);
|
|
108269
|
-
reorderBuffer.advanceTo(i + 1);
|
|
108270
|
-
job.framesRendered = i + 1;
|
|
108947
|
+
hdrEncoder.writeFrame(canvas);
|
|
108948
|
+
job.framesRendered = i + 1;
|
|
108949
|
+
if ((i + 1) % 10 === 0 || i + 1 === job.totalFrames) {
|
|
108271
108950
|
const frameProgress = (i + 1) / job.totalFrames;
|
|
108272
|
-
const progress = 25 + frameProgress * 55;
|
|
108273
108951
|
updateJobStatus(
|
|
108274
108952
|
job,
|
|
108275
108953
|
"rendering",
|
|
108276
|
-
`
|
|
108277
|
-
Math.round(
|
|
108954
|
+
`HDR composite frame ${i + 1}/${job.totalFrames}`,
|
|
108955
|
+
Math.round(25 + frameProgress * 55),
|
|
108278
108956
|
onProgress
|
|
108279
108957
|
);
|
|
108280
108958
|
}
|
|
108281
|
-
} finally {
|
|
108282
|
-
lastBrowserConsole = session.browserConsoleBuffer;
|
|
108283
|
-
await closeCaptureSession(session);
|
|
108284
108959
|
}
|
|
108960
|
+
} finally {
|
|
108961
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
108962
|
+
await closeCaptureSession(domSession);
|
|
108285
108963
|
}
|
|
108286
|
-
const
|
|
108964
|
+
const hdrEncodeResult = await hdrEncoder.close();
|
|
108287
108965
|
assertNotAborted();
|
|
108288
|
-
if (!
|
|
108289
|
-
throw new Error(`
|
|
108966
|
+
if (!hdrEncodeResult.success) {
|
|
108967
|
+
throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
|
|
108290
108968
|
}
|
|
108291
108969
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
108292
|
-
perfStages.encodeMs =
|
|
108970
|
+
perfStages.encodeMs = hdrEncodeResult.durationMs;
|
|
108293
108971
|
} else {
|
|
108294
|
-
|
|
108295
|
-
|
|
108296
|
-
await
|
|
108297
|
-
|
|
108298
|
-
|
|
108299
|
-
|
|
108300
|
-
|
|
108301
|
-
|
|
108302
|
-
|
|
108303
|
-
|
|
108304
|
-
|
|
108305
|
-
|
|
108306
|
-
|
|
108307
|
-
|
|
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;
|
|
108308
109060
|
updateJobStatus(
|
|
108309
109061
|
job,
|
|
108310
109062
|
"rendering",
|
|
108311
|
-
`
|
|
108312
|
-
Math.round(
|
|
109063
|
+
`Streaming frame ${i + 1}/${job.totalFrames}`,
|
|
109064
|
+
Math.round(progress),
|
|
108313
109065
|
onProgress
|
|
108314
109066
|
);
|
|
108315
109067
|
}
|
|
108316
|
-
}
|
|
108317
|
-
|
|
108318
|
-
|
|
108319
|
-
|
|
108320
|
-
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
108321
|
-
if (probeSession) {
|
|
108322
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
108323
|
-
await closeCaptureSession(probeSession);
|
|
108324
|
-
probeSession = null;
|
|
109068
|
+
} finally {
|
|
109069
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
109070
|
+
await closeCaptureSession(session);
|
|
109071
|
+
}
|
|
108325
109072
|
}
|
|
108326
|
-
|
|
108327
|
-
|
|
108328
|
-
|
|
108329
|
-
|
|
108330
|
-
framesDir,
|
|
108331
|
-
captureOptions,
|
|
108332
|
-
videoInjector,
|
|
108333
|
-
cfg
|
|
108334
|
-
);
|
|
108335
|
-
if (probeSession) {
|
|
108336
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
108337
|
-
probeSession = null;
|
|
109073
|
+
const encodeResult = await currentEncoder.close();
|
|
109074
|
+
assertNotAborted();
|
|
109075
|
+
if (!encodeResult.success) {
|
|
109076
|
+
throw new Error(`Streaming encode failed: ${encodeResult.error}`);
|
|
108338
109077
|
}
|
|
108339
|
-
|
|
108340
|
-
|
|
108341
|
-
|
|
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;
|
|
108342
109112
|
}
|
|
108343
|
-
|
|
108344
|
-
|
|
108345
|
-
|
|
108346
|
-
|
|
108347
|
-
|
|
108348
|
-
|
|
108349
|
-
|
|
108350
|
-
|
|
108351
|
-
|
|
108352
|
-
|
|
108353
|
-
|
|
108354
|
-
|
|
108355
|
-
`Capturing frame ${i + 1}/${job.totalFrames}`,
|
|
108356
|
-
Math.round(progress),
|
|
108357
|
-
onProgress
|
|
108358
|
-
);
|
|
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;
|
|
108359
109125
|
}
|
|
108360
|
-
|
|
108361
|
-
|
|
108362
|
-
|
|
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}`);
|
|
108363
109185
|
}
|
|
109186
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
108364
109187
|
}
|
|
108365
|
-
perfStages.captureMs = Date.now() - stage4Start;
|
|
108366
|
-
const stage5Start = Date.now();
|
|
108367
|
-
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
108368
|
-
const frameExt = needsAlpha ? "png" : "jpg";
|
|
108369
|
-
const framePattern = `frame_%06d.${frameExt}`;
|
|
108370
|
-
const encoderOpts = baseEncoderOpts;
|
|
108371
|
-
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
108372
|
-
framesDir,
|
|
108373
|
-
framePattern,
|
|
108374
|
-
videoOnlyPath,
|
|
108375
|
-
encoderOpts,
|
|
108376
|
-
chunkedEncodeSize,
|
|
108377
|
-
abortSignal
|
|
108378
|
-
) : await encodeFramesFromDir(
|
|
108379
|
-
framesDir,
|
|
108380
|
-
framePattern,
|
|
108381
|
-
videoOnlyPath,
|
|
108382
|
-
encoderOpts,
|
|
108383
|
-
abortSignal
|
|
108384
|
-
);
|
|
108385
|
-
assertNotAborted();
|
|
108386
|
-
if (!encodeResult.success) {
|
|
108387
|
-
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
108388
|
-
}
|
|
108389
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
108390
109188
|
}
|
|
108391
109189
|
if (probeSession !== null) {
|
|
108392
109190
|
const remainingProbeSession = probeSession;
|