@hyperframes/producer 0.8.198 → 0.8.200
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/distributed.js +176 -166
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -62
- package/dist/index.js +756 -451
- package/dist/public-server.js +566 -289
- package/dist/renderRequest.d.ts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/services/distributed/chunkWeights.d.ts +2 -0
- package/dist/services/hdrCompositor.d.ts +5 -5
- package/dist/services/hdrFrameWindows.d.ts +23 -0
- package/dist/services/htmlCompiler.d.ts +0 -2
- package/dist/services/render/audioPadTrim.d.ts +2 -0
- package/dist/services/render/renderEtaGuard.d.ts +35 -0
- package/dist/services/render/stages/captureHdrResources.d.ts +5 -17
- package/dist/services/render/stages/compileStage.d.ts +1 -1
- package/dist/services/render/stages/freezePlan.d.ts +1 -0
- package/dist/services/renderOrchestrator.d.ts +7 -6
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
mkdirSync as mkdirSync9,
|
|
6
6
|
readFileSync as readFileSync8,
|
|
7
7
|
readdirSync as readdirSync5,
|
|
8
|
-
rmSync as
|
|
9
|
-
statSync as
|
|
8
|
+
rmSync as rmSync11,
|
|
9
|
+
statSync as statSync7,
|
|
10
10
|
writeFileSync as writeFileSync5,
|
|
11
11
|
copyFileSync as copyFileSync4,
|
|
12
12
|
appendFileSync
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
augmentProtocolTimeoutError,
|
|
56
56
|
augmentPageNavigationTimeoutError
|
|
57
57
|
} from "@hyperframes/engine";
|
|
58
|
-
import { join as
|
|
58
|
+
import { join as join26, dirname as dirname10, resolve as resolve7 } from "path";
|
|
59
59
|
import { totalmem as totalmem2 } from "node:os";
|
|
60
60
|
import { randomUUID } from "crypto";
|
|
61
61
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -1890,8 +1890,8 @@ function replanAfterFailure(plan2, failure) {
|
|
|
1890
1890
|
}
|
|
1891
1891
|
|
|
1892
1892
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
1893
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as
|
|
1894
|
-
import { join as
|
|
1893
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
|
|
1894
|
+
import { join as join10 } from "node:path";
|
|
1895
1895
|
import {
|
|
1896
1896
|
captureFrameToBuffer as captureFrameToBuffer2,
|
|
1897
1897
|
closeCaptureSession as closeCaptureSession2,
|
|
@@ -2094,6 +2094,7 @@ function aggregateDrawElement(perfs, de) {
|
|
|
2094
2094
|
blankDeterministicAccepts: drain?.blankDeterministicAccepts ?? 0,
|
|
2095
2095
|
blankRecaptures: drain?.blankRecaptures ?? 0,
|
|
2096
2096
|
boundaryFrames: perfs.reduce((sum2, p) => sum2 + (p.deBoundaryFrames ?? 0), 0),
|
|
2097
|
+
riskFrames: perfs.reduce((sum2, p) => sum2 + (p.deRiskFrames ?? 0), 0),
|
|
2097
2098
|
ncprFallbacks: perfs.reduce((sum2, p) => sum2 + (p.deNcprFallbacks ?? 0), 0),
|
|
2098
2099
|
frameTimeouts: perfs.reduce((sum2, p) => sum2 + (p.deFrameTimeouts ?? 0), 0)
|
|
2099
2100
|
};
|
|
@@ -2275,7 +2276,7 @@ import {
|
|
|
2275
2276
|
} from "@hyperframes/engine";
|
|
2276
2277
|
|
|
2277
2278
|
// src/services/hdrCompositor.ts
|
|
2278
|
-
import { readSync as readSync2
|
|
2279
|
+
import { readSync as readSync2 } from "fs";
|
|
2279
2280
|
import { join as join6 } from "path";
|
|
2280
2281
|
import {
|
|
2281
2282
|
captureAlphaPng,
|
|
@@ -2328,10 +2329,10 @@ function cropRgb48le(src, srcW, srcH, cropX, cropY, cropW, cropH) {
|
|
|
2328
2329
|
}
|
|
2329
2330
|
function closeHdrVideoFrameSource(source, log) {
|
|
2330
2331
|
try {
|
|
2331
|
-
|
|
2332
|
+
source.windows.close();
|
|
2332
2333
|
} catch (err) {
|
|
2333
|
-
log?.warn("Failed to close HDR raw frame
|
|
2334
|
-
|
|
2334
|
+
log?.warn("Failed to close HDR raw frame windows", {
|
|
2335
|
+
dir: source.dir,
|
|
2335
2336
|
error: err instanceof Error ? err.message : String(err)
|
|
2336
2337
|
});
|
|
2337
2338
|
}
|
|
@@ -2341,7 +2342,7 @@ function resolveHdrVideoFrameIndex(time, startTime, fps, frameCount, loop = fals
|
|
|
2341
2342
|
if (frameIndex < 0 || frameCount < 1) return null;
|
|
2342
2343
|
return loop ? frameIndex % frameCount : Math.min(frameIndex, frameCount - 1);
|
|
2343
2344
|
}
|
|
2344
|
-
function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2345
|
+
async function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2345
2346
|
const frameSource = hdrVideoFrameSources.get(el.id);
|
|
2346
2347
|
const startTime = hdrStartTimes.get(el.id);
|
|
2347
2348
|
if (!frameSource || startTime === void 0 || el.opacity <= 0) {
|
|
@@ -2357,13 +2358,32 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
|
|
|
2357
2358
|
if (effectiveIndex === null) return;
|
|
2358
2359
|
const mediaTime = (frameSource.mediaStart ?? 0) + effectiveIndex / fps;
|
|
2359
2360
|
if (isVideoHiddenBeforeStreamStart(frameSource.videoStreamLeadSeconds, mediaTime)) return;
|
|
2360
|
-
|
|
2361
|
+
if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
|
|
2362
|
+
const lease = await frameSource.windows.lease(effectiveIndex);
|
|
2363
|
+
if (!lease) return;
|
|
2364
|
+
try {
|
|
2365
|
+
blitScratchFrameWithoutYielding(
|
|
2366
|
+
canvas,
|
|
2367
|
+
el,
|
|
2368
|
+
frameSource,
|
|
2369
|
+
lease,
|
|
2370
|
+
width,
|
|
2371
|
+
height,
|
|
2372
|
+
log,
|
|
2373
|
+
sourceTransfer,
|
|
2374
|
+
targetTransfer,
|
|
2375
|
+
hdrPerf
|
|
2376
|
+
);
|
|
2377
|
+
} finally {
|
|
2378
|
+
lease.release();
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function blitScratchFrameWithoutYielding(canvas, el, frameSource, lease, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2361
2382
|
try {
|
|
2362
|
-
if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
|
|
2363
2383
|
const bytesRead = timeHdrPhase(
|
|
2364
2384
|
hdrPerf,
|
|
2365
2385
|
"hdrVideoReadDecodeMs",
|
|
2366
|
-
() => readSync2(
|
|
2386
|
+
() => readSync2(lease.fd, frameSource.scratch, 0, frameSource.frameSize, lease.offset)
|
|
2367
2387
|
);
|
|
2368
2388
|
if (bytesRead !== frameSource.frameSize) return;
|
|
2369
2389
|
const hdrRgb = frameSource.scratch;
|
|
@@ -2592,7 +2612,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2592
2612
|
hdrPerf
|
|
2593
2613
|
);
|
|
2594
2614
|
} else {
|
|
2595
|
-
blitHdrVideoLayer(
|
|
2615
|
+
await blitHdrVideoLayer(
|
|
2596
2616
|
canvas,
|
|
2597
2617
|
layer.element,
|
|
2598
2618
|
time,
|
|
@@ -2636,7 +2656,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2636
2656
|
startTime,
|
|
2637
2657
|
localTime: localTime.toFixed(3),
|
|
2638
2658
|
hdrFrameNum: frameNum,
|
|
2639
|
-
|
|
2659
|
+
dir: frameSource?.dir ?? null,
|
|
2640
2660
|
frameCount: frameSource?.frameCount ?? null
|
|
2641
2661
|
});
|
|
2642
2662
|
}
|
|
@@ -2732,18 +2752,8 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2732
2752
|
}
|
|
2733
2753
|
|
|
2734
2754
|
// src/services/render/stages/captureHdrResources.ts
|
|
2735
|
-
import {
|
|
2736
|
-
|
|
2737
|
-
constants as constants2,
|
|
2738
|
-
fstatSync as fstatSync3,
|
|
2739
|
-
mkdirSync as mkdirSync2,
|
|
2740
|
-
mkdtempSync as mkdtempSync2,
|
|
2741
|
-
openSync as openSync3,
|
|
2742
|
-
readFileSync as readFileSync3,
|
|
2743
|
-
rmSync as rmSync4,
|
|
2744
|
-
statfsSync
|
|
2745
|
-
} from "node:fs";
|
|
2746
|
-
import { join as join7 } from "node:path";
|
|
2755
|
+
import { mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, rmSync as rmSync5, statSync as statSync2, statfsSync } from "node:fs";
|
|
2756
|
+
import { join as join8 } from "node:path";
|
|
2747
2757
|
import {
|
|
2748
2758
|
decodePngToRgb48le,
|
|
2749
2759
|
extractMediaMetadata as extractMediaMetadata2,
|
|
@@ -2757,7 +2767,90 @@ import {
|
|
|
2757
2767
|
runFfmpeg
|
|
2758
2768
|
} from "@hyperframes/engine";
|
|
2759
2769
|
import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
2760
|
-
|
|
2770
|
+
|
|
2771
|
+
// src/services/hdrFrameWindows.ts
|
|
2772
|
+
import { closeSync as closeSync3, constants as constants2, openSync as openSync3, rmSync as rmSync4 } from "node:fs";
|
|
2773
|
+
import { join as join7 } from "node:path";
|
|
2774
|
+
function createHdrFrameWindows(opts) {
|
|
2775
|
+
const { dir, frameSize, windowFrames, maxResident, extract } = opts;
|
|
2776
|
+
let totalFrames = opts.totalFrames;
|
|
2777
|
+
const windows = /* @__PURE__ */ new Map();
|
|
2778
|
+
const drop = (index, w) => {
|
|
2779
|
+
windows.delete(index);
|
|
2780
|
+
if (w.fd >= 0) closeSync3(w.fd);
|
|
2781
|
+
rmSync4(w.path, { force: true });
|
|
2782
|
+
};
|
|
2783
|
+
const open2 = (index) => {
|
|
2784
|
+
const known = windows.get(index);
|
|
2785
|
+
if (known) {
|
|
2786
|
+
windows.delete(index);
|
|
2787
|
+
windows.set(index, known);
|
|
2788
|
+
return known;
|
|
2789
|
+
}
|
|
2790
|
+
for (const [i, w2] of windows) {
|
|
2791
|
+
if (windows.size < maxResident) break;
|
|
2792
|
+
if (w2.pins === 0) drop(i, w2);
|
|
2793
|
+
}
|
|
2794
|
+
const firstFrame = index * windowFrames;
|
|
2795
|
+
const w = {
|
|
2796
|
+
firstFrame,
|
|
2797
|
+
path: join7(dir, `window-${index}.rgb48le`),
|
|
2798
|
+
frames: 0,
|
|
2799
|
+
fd: -1,
|
|
2800
|
+
pins: 0,
|
|
2801
|
+
ready: Promise.resolve()
|
|
2802
|
+
};
|
|
2803
|
+
w.ready = extract({
|
|
2804
|
+
firstFrame,
|
|
2805
|
+
frames: Math.min(windowFrames, totalFrames - firstFrame),
|
|
2806
|
+
path: w.path
|
|
2807
|
+
}).then((produced) => {
|
|
2808
|
+
if (produced < 1) return;
|
|
2809
|
+
w.fd = openSync3(w.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
2810
|
+
w.frames = produced;
|
|
2811
|
+
});
|
|
2812
|
+
windows.set(index, w);
|
|
2813
|
+
return w;
|
|
2814
|
+
};
|
|
2815
|
+
return {
|
|
2816
|
+
get totalFrames() {
|
|
2817
|
+
return totalFrames;
|
|
2818
|
+
},
|
|
2819
|
+
async lease(frameIndex) {
|
|
2820
|
+
for (; ; ) {
|
|
2821
|
+
if (totalFrames < 1) return null;
|
|
2822
|
+
const index = Math.min(Math.max(0, frameIndex), totalFrames - 1);
|
|
2823
|
+
const key = Math.floor(index / windowFrames);
|
|
2824
|
+
const w = open2(key);
|
|
2825
|
+
w.pins += 1;
|
|
2826
|
+
try {
|
|
2827
|
+
await w.ready;
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
w.pins -= 1;
|
|
2830
|
+
windows.delete(key);
|
|
2831
|
+
throw error;
|
|
2832
|
+
}
|
|
2833
|
+
const local = index - w.firstFrame;
|
|
2834
|
+
if (local < w.frames) {
|
|
2835
|
+
return {
|
|
2836
|
+
fd: w.fd,
|
|
2837
|
+
offset: local * frameSize,
|
|
2838
|
+
release: () => {
|
|
2839
|
+
w.pins -= 1;
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
w.pins -= 1;
|
|
2844
|
+
totalFrames = Math.min(totalFrames, w.firstFrame + w.frames);
|
|
2845
|
+
}
|
|
2846
|
+
},
|
|
2847
|
+
close() {
|
|
2848
|
+
for (const [i, w] of [...windows]) drop(i, w);
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// src/services/render/stages/captureHdrResources.ts
|
|
2761
2854
|
function tempDirSafePrefix(id) {
|
|
2762
2855
|
const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
|
|
2763
2856
|
return safe || "video";
|
|
@@ -2802,10 +2895,16 @@ async function probeHdrExtractionDims(args) {
|
|
|
2802
2895
|
const stacking = await queryElementStacking(domSession.page, nativeHdrIds);
|
|
2803
2896
|
for (const el of stacking) {
|
|
2804
2897
|
if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !prep.hdrExtractionDims.has(el.id)) {
|
|
2805
|
-
prep.hdrExtractionDims.set(el.id, {
|
|
2898
|
+
prep.hdrExtractionDims.set(el.id, {
|
|
2899
|
+
width: el.layoutWidth,
|
|
2900
|
+
height: el.layoutHeight
|
|
2901
|
+
});
|
|
2806
2902
|
}
|
|
2807
2903
|
if (el.isHdr && nativeHdrImageIds.has(el.id) && !prep.hdrImageFitInfo.has(el.id)) {
|
|
2808
|
-
prep.hdrImageFitInfo.set(el.id, {
|
|
2904
|
+
prep.hdrImageFitInfo.set(el.id, {
|
|
2905
|
+
fit: el.objectFit,
|
|
2906
|
+
position: el.objectPosition
|
|
2907
|
+
});
|
|
2809
2908
|
}
|
|
2810
2909
|
}
|
|
2811
2910
|
}
|
|
@@ -2824,23 +2923,21 @@ async function probeHdrExtractionDims(args) {
|
|
|
2824
2923
|
const retryStacking = await queryElementStacking(domSession.page, nativeHdrIds);
|
|
2825
2924
|
for (const el of retryStacking) {
|
|
2826
2925
|
if (el.id === imageId && el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0) {
|
|
2827
|
-
prep.hdrExtractionDims.set(el.id, {
|
|
2926
|
+
prep.hdrExtractionDims.set(el.id, {
|
|
2927
|
+
width: el.layoutWidth,
|
|
2928
|
+
height: el.layoutHeight
|
|
2929
|
+
});
|
|
2828
2930
|
if (!prep.hdrImageFitInfo.has(el.id)) {
|
|
2829
|
-
prep.hdrImageFitInfo.set(el.id, {
|
|
2931
|
+
prep.hdrImageFitInfo.set(el.id, {
|
|
2932
|
+
fit: el.objectFit,
|
|
2933
|
+
position: el.objectPosition
|
|
2934
|
+
});
|
|
2830
2935
|
}
|
|
2831
2936
|
break;
|
|
2832
2937
|
}
|
|
2833
2938
|
}
|
|
2834
2939
|
}
|
|
2835
2940
|
}
|
|
2836
|
-
function estimateHdrExtractionBytes(videos, fps) {
|
|
2837
|
-
let total = 0;
|
|
2838
|
-
for (const v of videos) {
|
|
2839
|
-
const frames = Math.ceil(Math.max(0, v.durationSeconds) * fps);
|
|
2840
|
-
total += frames * v.width * v.height * 6;
|
|
2841
|
-
}
|
|
2842
|
-
return total;
|
|
2843
|
-
}
|
|
2844
2941
|
var HDR_EXTRACTION_HEADROOM_FRACTION = 0.9;
|
|
2845
2942
|
var HDR_EXTRACTION_CGROUP_BUDGET_FRACTION = 0.5;
|
|
2846
2943
|
var HDR_EXTRACTION_WARN_BYTES = 1e10;
|
|
@@ -2904,24 +3001,22 @@ function resolveHdrExtractionWindow(video, compositionDuration, metadata) {
|
|
|
2904
3001
|
}
|
|
2905
3002
|
return window2;
|
|
2906
3003
|
}
|
|
2907
|
-
function cleanupHdrFrameDirectory(frameDir,
|
|
3004
|
+
function cleanupHdrFrameDirectory(frameDir, log) {
|
|
2908
3005
|
if (process.env.KEEP_TEMP === "1") return;
|
|
2909
3006
|
try {
|
|
2910
|
-
|
|
3007
|
+
rmSync5(frameDir, { recursive: true, force: true });
|
|
2911
3008
|
} catch (err) {
|
|
2912
3009
|
log?.warn("Failed to clean up HDR raw frame directory", {
|
|
2913
3010
|
frameDir,
|
|
2914
|
-
rawPath,
|
|
2915
3011
|
error: err instanceof Error ? err.message : String(err)
|
|
2916
3012
|
});
|
|
2917
3013
|
}
|
|
2918
3014
|
}
|
|
2919
3015
|
function cleanupHdrVideoFrameSource(source, log) {
|
|
2920
3016
|
closeHdrVideoFrameSource(source, log);
|
|
2921
|
-
cleanupHdrFrameDirectory(source.dir,
|
|
3017
|
+
cleanupHdrFrameDirectory(source.dir, log);
|
|
2922
3018
|
}
|
|
2923
|
-
function assertHdrExtractionDiskHeadroom(framesDir,
|
|
2924
|
-
const estimatedBytes = estimateHdrExtractionBytes(plannedVideos, fps);
|
|
3019
|
+
function assertHdrExtractionDiskHeadroom(framesDir, estimatedBytes, log) {
|
|
2925
3020
|
const configuredBudget = resolveHdrExtractionBudgetBytes(
|
|
2926
3021
|
process.env[HDR_EXTRACTION_MAX_BYTES_ENV]
|
|
2927
3022
|
);
|
|
@@ -2955,14 +3050,29 @@ function assertHdrExtractionDiskHeadroom(framesDir, plannedVideos, fps, log) {
|
|
|
2955
3050
|
budgetBytes: resolveHdrExtractionActiveBudgetBytes(configuredBudget, freeBytes)
|
|
2956
3051
|
};
|
|
2957
3052
|
}
|
|
3053
|
+
var DEFAULT_HDR_WINDOW_BYTES = 512 * BYTES_PER_MIB;
|
|
3054
|
+
function hdrWindowFrames(frameSize, windowBytes) {
|
|
3055
|
+
return Math.max(8, Math.floor(windowBytes / frameSize));
|
|
3056
|
+
}
|
|
3057
|
+
async function probeFirstTick(srcPath, mediaStart, fps, runFfmpegImpl, abortSignal) {
|
|
3058
|
+
const args = ["-ss", String(mediaStart), "-i", srcPath, "-frames:v", "1"];
|
|
3059
|
+
const result = await runFfmpegImpl([...args, ...["-vf", "showinfo", "-f", "null", "-"]], {
|
|
3060
|
+
signal: abortSignal
|
|
3061
|
+
});
|
|
3062
|
+
const match = /pts_time:(-?[\d.]+)/.exec(result.stderr);
|
|
3063
|
+
return match ? Math.max(0, Math.floor(Number(match[1]) * fps + 0.5)) : 0;
|
|
3064
|
+
}
|
|
2958
3065
|
async function extractHdrVideoFrames(args) {
|
|
2959
3066
|
const { job, log, framesDir, composition, prep, width, height, abortSignal, hdrDiagnostics } = args;
|
|
3067
|
+
const maxResident = Math.max(2, args.maxResidentWindows ?? 2);
|
|
3068
|
+
const windowBytes = args.windowBytes ?? DEFAULT_HDR_WINDOW_BYTES;
|
|
2960
3069
|
const runFfmpegImpl = args.runFfmpegImpl ?? runFfmpeg;
|
|
2961
3070
|
const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata2;
|
|
2962
3071
|
const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
|
|
3072
|
+
const fps = fpsToNumber4(job.config.fps);
|
|
2963
3073
|
const out = /* @__PURE__ */ new Map();
|
|
2964
3074
|
mkdirSync2(framesDir, { recursive: true });
|
|
2965
|
-
|
|
3075
|
+
let scratchBytes = 0;
|
|
2966
3076
|
const extractionWindows = /* @__PURE__ */ new Map();
|
|
2967
3077
|
for (const [videoId, srcPath] of prep.hdrVideoSrcPaths) {
|
|
2968
3078
|
const video = composition.videos.find((v) => v.id === videoId);
|
|
@@ -2983,16 +3093,13 @@ async function extractHdrVideoFrames(args) {
|
|
|
2983
3093
|
videoStreamLeadSeconds: metadata.videoStreamLeadSeconds
|
|
2984
3094
|
});
|
|
2985
3095
|
prep.hdrVideoStartTimes.set(videoId, window2.compositionStart);
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
height: dims.height
|
|
2990
|
-
});
|
|
3096
|
+
const frameSize = dims.width * dims.height * 6;
|
|
3097
|
+
const frames = window2.finalFrameOnly ? 1 : Math.ceil(window2.durationSeconds * fps);
|
|
3098
|
+
scratchBytes += Math.min(frames, maxResident * hdrWindowFrames(frameSize, windowBytes)) * frameSize;
|
|
2991
3099
|
}
|
|
2992
3100
|
const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
|
|
2993
3101
|
framesDir,
|
|
2994
|
-
|
|
2995
|
-
fpsToNumber4(job.config.fps),
|
|
3102
|
+
scratchBytes,
|
|
2996
3103
|
log
|
|
2997
3104
|
);
|
|
2998
3105
|
const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
|
|
@@ -3004,79 +3111,79 @@ async function extractHdrVideoFrames(args) {
|
|
|
3004
3111
|
if (!video || !planned) continue;
|
|
3005
3112
|
const { window: window2 } = planned;
|
|
3006
3113
|
mkdirSync2(framesDir, { recursive: true });
|
|
3007
|
-
const frameDir = mkdtempSync2(
|
|
3114
|
+
const frameDir = mkdtempSync2(join8(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
|
|
3008
3115
|
createdFrameDirs.add(frameDir);
|
|
3009
3116
|
const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
|
|
3010
|
-
const rawPath = join7(frameDir, "frames.rgb48le");
|
|
3011
|
-
const extractionMediaStart = window2.extractionMediaStart ?? window2.mediaStart;
|
|
3012
|
-
const extractionStart = String(extractionMediaStart);
|
|
3013
|
-
const ffmpegArgs = [];
|
|
3014
|
-
if (window2.finalFrameOnly) {
|
|
3015
|
-
ffmpegArgs.push("-i", srcPath, "-ss", extractionStart, "-frames:v", "1");
|
|
3016
|
-
} else {
|
|
3017
|
-
ffmpegArgs.push(
|
|
3018
|
-
"-ss",
|
|
3019
|
-
extractionStart,
|
|
3020
|
-
"-i",
|
|
3021
|
-
srcPath,
|
|
3022
|
-
"-t",
|
|
3023
|
-
String(window2.durationSeconds)
|
|
3024
|
-
);
|
|
3025
|
-
}
|
|
3026
|
-
if (!window2.finalFrameOnly) {
|
|
3027
|
-
ffmpegArgs.push("-r", fpsToFfmpegArg(job.config.fps));
|
|
3028
|
-
}
|
|
3029
|
-
ffmpegArgs.push(
|
|
3030
|
-
"-vf",
|
|
3031
|
-
`scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`,
|
|
3032
|
-
"-pix_fmt",
|
|
3033
|
-
"rgb48le",
|
|
3034
|
-
"-f",
|
|
3035
|
-
"rawvideo",
|
|
3036
|
-
"-y",
|
|
3037
|
-
rawPath
|
|
3038
|
-
);
|
|
3039
|
-
const result = await runFfmpegImpl(ffmpegArgs, { signal: abortSignal });
|
|
3040
|
-
if (!result.success) {
|
|
3041
|
-
hdrDiagnostics.videoExtractionFailures += 1;
|
|
3042
|
-
log.error("HDR frame pre-extraction failed; aborting render", {
|
|
3043
|
-
videoId,
|
|
3044
|
-
srcPath,
|
|
3045
|
-
stderr: result.stderr.slice(-400)
|
|
3046
|
-
});
|
|
3047
|
-
throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
|
|
3048
|
-
error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
|
|
3049
|
-
failureReason: result.failureReason
|
|
3050
|
-
});
|
|
3051
|
-
}
|
|
3052
3117
|
const frameSize = dims.width * dims.height * 6;
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3118
|
+
const extractionMediaStart = window2.extractionMediaStart ?? window2.mediaStart;
|
|
3119
|
+
const scale = `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`;
|
|
3120
|
+
const firstTick = window2.finalFrameOnly ? 0 : await probeFirstTick(srcPath, extractionMediaStart, fps, runFfmpegImpl, abortSignal);
|
|
3121
|
+
const totalFrames = window2.finalFrameOnly ? 1 : Math.max(1, Math.ceil(window2.durationSeconds * fps - firstTick - 1e-9)) + Number(video.loop);
|
|
3122
|
+
const windowFrames = Math.min(totalFrames, hdrWindowFrames(frameSize, windowBytes));
|
|
3123
|
+
const windows = createHdrFrameWindows({
|
|
3124
|
+
dir: frameDir,
|
|
3125
|
+
frameSize,
|
|
3126
|
+
totalFrames,
|
|
3127
|
+
windowFrames,
|
|
3128
|
+
maxResident,
|
|
3129
|
+
extract: async (spec) => {
|
|
3130
|
+
const ffmpegArgs = window2.finalFrameOnly ? (
|
|
3131
|
+
// Decode before seeking for the one-frame path. Input-side seeking can
|
|
3132
|
+
// return zero frames for valid unindexed/negative-base transports.
|
|
3133
|
+
["-i", srcPath, "-ss", String(extractionMediaStart), "-frames:v", "1", "-vf", scale]
|
|
3134
|
+
) : hdrWindowFfmpegArgs({
|
|
3135
|
+
srcPath,
|
|
3136
|
+
mediaStart: extractionMediaStart,
|
|
3137
|
+
fps,
|
|
3138
|
+
fpsArg: fpsToFfmpegArg(job.config.fps),
|
|
3139
|
+
firstTick,
|
|
3140
|
+
firstFrame: spec.firstFrame,
|
|
3141
|
+
frames: spec.frames,
|
|
3142
|
+
scale
|
|
3143
|
+
});
|
|
3144
|
+
const result = await runFfmpegImpl(
|
|
3145
|
+
[...ffmpegArgs, "-pix_fmt", "rgb48le", "-f", "rawvideo", "-y", spec.path],
|
|
3146
|
+
{ signal: abortSignal }
|
|
3061
3147
|
);
|
|
3148
|
+
if (!result.success) {
|
|
3149
|
+
hdrDiagnostics.videoExtractionFailures += 1;
|
|
3150
|
+
log.error("HDR frame extraction failed; aborting render", {
|
|
3151
|
+
videoId,
|
|
3152
|
+
srcPath,
|
|
3153
|
+
firstFrame: spec.firstFrame,
|
|
3154
|
+
stderr: result.stderr.slice(-400)
|
|
3155
|
+
});
|
|
3156
|
+
throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
|
|
3157
|
+
error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
|
|
3158
|
+
failureReason: result.failureReason
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
return Math.min(Math.floor(statSync2(spec.path).size / frameSize), spec.frames);
|
|
3062
3162
|
}
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
frameCount,
|
|
3071
|
-
scratch: Buffer.allocUnsafe(frameSize),
|
|
3072
|
-
loop: video.loop,
|
|
3073
|
-
mediaStart: extractionMediaStart,
|
|
3074
|
-
videoStreamLeadSeconds: planned.videoStreamLeadSeconds
|
|
3075
|
-
});
|
|
3076
|
-
handedOff = true;
|
|
3077
|
-
} finally {
|
|
3078
|
-
if (!handedOff) closeSync4(fd);
|
|
3163
|
+
});
|
|
3164
|
+
const first = await windows.lease(0);
|
|
3165
|
+
if (!first) {
|
|
3166
|
+
hdrDiagnostics.videoExtractionFailures += 1;
|
|
3167
|
+
throw new Error(
|
|
3168
|
+
`HDR frame extraction produced no frames for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
|
|
3169
|
+
);
|
|
3079
3170
|
}
|
|
3171
|
+
first.release();
|
|
3172
|
+
if (video.loop) (await windows.lease(totalFrames - 1))?.release();
|
|
3173
|
+
out.set(videoId, {
|
|
3174
|
+
dir: frameDir,
|
|
3175
|
+
windows,
|
|
3176
|
+
width: dims.width,
|
|
3177
|
+
height: dims.height,
|
|
3178
|
+
frameSize,
|
|
3179
|
+
get frameCount() {
|
|
3180
|
+
return windows.totalFrames;
|
|
3181
|
+
},
|
|
3182
|
+
scratch: Buffer.allocUnsafe(frameSize),
|
|
3183
|
+
loop: video.loop,
|
|
3184
|
+
mediaStart: extractionMediaStart,
|
|
3185
|
+
videoStreamLeadSeconds: planned.videoStreamLeadSeconds
|
|
3186
|
+
});
|
|
3080
3187
|
}
|
|
3081
3188
|
return { sources: out, estimatedBytes, releaseReservation };
|
|
3082
3189
|
} catch (error) {
|
|
@@ -3085,12 +3192,23 @@ async function extractHdrVideoFrames(args) {
|
|
|
3085
3192
|
createdFrameDirs.delete(source.dir);
|
|
3086
3193
|
}
|
|
3087
3194
|
for (const frameDir of createdFrameDirs) {
|
|
3088
|
-
cleanupHdrFrameDirectory(frameDir,
|
|
3195
|
+
cleanupHdrFrameDirectory(frameDir, log);
|
|
3089
3196
|
}
|
|
3090
3197
|
releaseReservation();
|
|
3091
3198
|
throw error;
|
|
3092
3199
|
}
|
|
3093
3200
|
}
|
|
3201
|
+
function hdrWindowFfmpegArgs(a) {
|
|
3202
|
+
const unit = Math.ceil(a.fps);
|
|
3203
|
+
const seekFrame = a.firstFrame < unit ? 0 : (Math.floor(a.firstFrame / unit) - 1) * unit;
|
|
3204
|
+
const trim = a.firstFrame - seekFrame;
|
|
3205
|
+
const filters = [`fps=${a.fpsArg}:start_time=${(a.firstTick / a.fps).toFixed(6)}`, a.scale];
|
|
3206
|
+
if (trim > 0) filters.push(`trim=start_frame=${trim}`);
|
|
3207
|
+
return [
|
|
3208
|
+
...["-ss", (a.mediaStart + seekFrame / a.fps).toFixed(6), "-i", a.srcPath],
|
|
3209
|
+
...["-vf", filters.join(","), "-frames:v", String(a.frames)]
|
|
3210
|
+
];
|
|
3211
|
+
}
|
|
3094
3212
|
function decodeHdrImageBuffers(args) {
|
|
3095
3213
|
const { log, hdrImageSrcPaths, prep, hdrDiagnostics } = args;
|
|
3096
3214
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3110,7 +3228,11 @@ function decodeHdrImageBuffers(args) {
|
|
|
3110
3228
|
fit,
|
|
3111
3229
|
fitInfo?.position
|
|
3112
3230
|
);
|
|
3113
|
-
out.set(imageId, {
|
|
3231
|
+
out.set(imageId, {
|
|
3232
|
+
data: resampled,
|
|
3233
|
+
width: layout.width,
|
|
3234
|
+
height: layout.height
|
|
3235
|
+
});
|
|
3114
3236
|
} else {
|
|
3115
3237
|
out.set(imageId, {
|
|
3116
3238
|
data: Buffer.from(decoded.data),
|
|
@@ -3222,7 +3344,7 @@ async function captureSceneIntoBuffer(a) {
|
|
|
3222
3344
|
hdrPerf
|
|
3223
3345
|
);
|
|
3224
3346
|
} else {
|
|
3225
|
-
blitHdrVideoLayer(
|
|
3347
|
+
await blitHdrVideoLayer(
|
|
3226
3348
|
sceneBuf,
|
|
3227
3349
|
el,
|
|
3228
3350
|
time,
|
|
@@ -3424,7 +3546,7 @@ function isTargetLossError(error) {
|
|
|
3424
3546
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3425
3547
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
3426
3548
|
import { tmpdir } from "node:os";
|
|
3427
|
-
import { join as
|
|
3549
|
+
import { join as join9 } from "node:path";
|
|
3428
3550
|
import {
|
|
3429
3551
|
DrawElementVerificationError,
|
|
3430
3552
|
captureFrameToBuffer,
|
|
@@ -3586,11 +3708,11 @@ function createDrainFrameGuard(args) {
|
|
|
3586
3708
|
return buf;
|
|
3587
3709
|
}
|
|
3588
3710
|
if (db < verifyMinDb) {
|
|
3589
|
-
const dumpDir = await mkdtemp(
|
|
3711
|
+
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3590
3712
|
if (dumpDir) {
|
|
3591
3713
|
await Promise.all([
|
|
3592
|
-
writeFile(
|
|
3593
|
-
writeFile(
|
|
3714
|
+
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3715
|
+
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3594
3716
|
]).catch(() => {
|
|
3595
3717
|
});
|
|
3596
3718
|
}
|
|
@@ -3609,11 +3731,21 @@ function createDrainFrameGuard(args) {
|
|
|
3609
3731
|
return buf;
|
|
3610
3732
|
};
|
|
3611
3733
|
}
|
|
3612
|
-
async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log, stats, abortSignal) {
|
|
3734
|
+
async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log, stats, abortSignal, risk) {
|
|
3613
3735
|
let prev = null;
|
|
3614
3736
|
const frameTime = (i) => i * job.config.fps.den / job.config.fps.num;
|
|
3615
3737
|
const guard = createDrainFrameGuard({ log, stats, frameTime });
|
|
3616
|
-
const guardFrame = (
|
|
3738
|
+
const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
|
|
3739
|
+
const captureRisk = async (idx) => {
|
|
3740
|
+
await risk.open();
|
|
3741
|
+
lastProgressAt = Date.now();
|
|
3742
|
+
return raceAgainstStall(
|
|
3743
|
+
risk.capture(idx, frameTime(idx)),
|
|
3744
|
+
stallTimeoutMs - (Date.now() - lastProgressAt),
|
|
3745
|
+
{ session, frameIndex: idx, totalFrames, stallTimeoutMs },
|
|
3746
|
+
abortSignal
|
|
3747
|
+
);
|
|
3748
|
+
};
|
|
3617
3749
|
const stallTimeoutMs = resolveCaptureStallTimeoutMs();
|
|
3618
3750
|
let lastProgressAt = Date.now();
|
|
3619
3751
|
const captureWithStallGuard = (idx, promise) => raceAgainstStall(
|
|
@@ -3625,7 +3757,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3625
3757
|
const drainPrev = async () => {
|
|
3626
3758
|
if (!prev) return;
|
|
3627
3759
|
assertNotAborted();
|
|
3628
|
-
const buf = await guardFrame(prev
|
|
3760
|
+
const buf = await guardFrame(prev, await prev.encodeResult);
|
|
3629
3761
|
await reorderBuffer.waitForFrame(prev.idx);
|
|
3630
3762
|
ensureFrameWritten(await currentEncoder.writeFrame(buf), prev.idx, currentEncoder);
|
|
3631
3763
|
reorderBuffer.advanceTo(prev.idx + 1);
|
|
@@ -3645,7 +3777,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3645
3777
|
const drainBatch = async (batch) => {
|
|
3646
3778
|
for (const item of batch) {
|
|
3647
3779
|
assertNotAborted();
|
|
3648
|
-
const buf = await guardFrame(item
|
|
3780
|
+
const buf = await guardFrame(item, await item.encodeResult);
|
|
3649
3781
|
await reorderBuffer.waitForFrame(item.idx);
|
|
3650
3782
|
ensureFrameWritten(await currentEncoder.writeFrame(buf), item.idx, currentEncoder);
|
|
3651
3783
|
reorderBuffer.advanceTo(item.idx + 1);
|
|
@@ -3663,7 +3795,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3663
3795
|
};
|
|
3664
3796
|
if (batchN > 1 && !session.onBeforeCapture) {
|
|
3665
3797
|
const boundarySS = process.env.HF_FAST_CAPTURE_BOUNDARY_SS === "true";
|
|
3666
|
-
const batchable = (f) => !session.staticFrames?.has(f) && !(boundarySS && session.clipBoundaryFrames?.has(f));
|
|
3798
|
+
const batchable = (f) => !session.staticFrames?.has(f) && !(boundarySS && session.clipBoundaryFrames?.has(f)) && !risk?.frames.has(f);
|
|
3667
3799
|
let prevBatch = [];
|
|
3668
3800
|
let i = 0;
|
|
3669
3801
|
while (i < totalFrames) {
|
|
@@ -3680,6 +3812,11 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3680
3812
|
);
|
|
3681
3813
|
await drainBatch(prevBatch);
|
|
3682
3814
|
prevBatch = results.map((r) => ({ idx: r.frameIndex, encodeResult: r.encodeResult }));
|
|
3815
|
+
} else if (risk?.frames.has(i)) {
|
|
3816
|
+
const buffer = await captureRisk(i);
|
|
3817
|
+
await drainBatch(prevBatch);
|
|
3818
|
+
prevBatch = [{ idx: i, encodeResult: Promise.resolve(buffer), trusted: true }];
|
|
3819
|
+
i++;
|
|
3683
3820
|
} else {
|
|
3684
3821
|
const { encodeResult } = await captureWithStallGuard(
|
|
3685
3822
|
i,
|
|
@@ -3695,6 +3832,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3695
3832
|
}
|
|
3696
3833
|
for (let i = 0; i < totalFrames; i++) {
|
|
3697
3834
|
assertNotAborted();
|
|
3835
|
+
if (risk?.frames.has(i)) {
|
|
3836
|
+
const buffer = await captureRisk(i);
|
|
3837
|
+
await drainPrev();
|
|
3838
|
+
prev = { idx: i, encodeResult: Promise.resolve(buffer), trusted: true };
|
|
3839
|
+
continue;
|
|
3840
|
+
}
|
|
3698
3841
|
const time = frameTime(i);
|
|
3699
3842
|
const { encodeResult } = await captureWithStallGuard(
|
|
3700
3843
|
i,
|
|
@@ -3941,6 +4084,24 @@ async function runCaptureStreamingStage(input) {
|
|
|
3941
4084
|
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
3942
4085
|
probeSession = null;
|
|
3943
4086
|
}
|
|
4087
|
+
let riskSession = null;
|
|
4088
|
+
const openRiskSession = async () => {
|
|
4089
|
+
const opened = await createCaptureSession(
|
|
4090
|
+
fileServer.url,
|
|
4091
|
+
framesDir,
|
|
4092
|
+
buildCaptureOptions(),
|
|
4093
|
+
createRenderVideoFrameInjector(),
|
|
4094
|
+
{
|
|
4095
|
+
...captureCfg,
|
|
4096
|
+
useDrawElement: false,
|
|
4097
|
+
staticFrameDedup: false,
|
|
4098
|
+
enableBrowserPool: false
|
|
4099
|
+
}
|
|
4100
|
+
);
|
|
4101
|
+
riskSession = opened;
|
|
4102
|
+
await initializeSession(opened);
|
|
4103
|
+
return opened;
|
|
4104
|
+
};
|
|
3944
4105
|
try {
|
|
3945
4106
|
if (!session.isInitialized) {
|
|
3946
4107
|
await initializeSession(session);
|
|
@@ -3955,6 +4116,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
3955
4116
|
blankDeterministicAccepts: 0,
|
|
3956
4117
|
blankRecaptures: 0
|
|
3957
4118
|
};
|
|
4119
|
+
const riskFrames = session.deRiskFrames;
|
|
3958
4120
|
await runWorkerEncodePipelineLoop(
|
|
3959
4121
|
session,
|
|
3960
4122
|
totalFrames,
|
|
@@ -3965,7 +4127,16 @@ async function runCaptureStreamingStage(input) {
|
|
|
3965
4127
|
onProgress,
|
|
3966
4128
|
log,
|
|
3967
4129
|
deDrainStats,
|
|
3968
|
-
abortSignal
|
|
4130
|
+
abortSignal,
|
|
4131
|
+
riskFrames && riskFrames.size > 0 ? {
|
|
4132
|
+
frames: riskFrames,
|
|
4133
|
+
// Opened on the first risk frame, in a browser of its own: pages sharing a
|
|
4134
|
+
// browser share a compositor, which starves the drawElement page's paint events.
|
|
4135
|
+
open: async () => {
|
|
4136
|
+
riskSession ??= await openRiskSession();
|
|
4137
|
+
},
|
|
4138
|
+
capture: async (frameIndex, time) => (await captureFrameToBuffer(riskSession, frameIndex, time)).buffer
|
|
4139
|
+
} : void 0
|
|
3969
4140
|
);
|
|
3970
4141
|
} else {
|
|
3971
4142
|
const stallTimeoutMs = resolveCaptureStallTimeoutMs();
|
|
@@ -4003,6 +4174,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4003
4174
|
} finally {
|
|
4004
4175
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4005
4176
|
await closeCaptureSession(session);
|
|
4177
|
+
if (riskSession) await closeCaptureSession(riskSession);
|
|
4006
4178
|
}
|
|
4007
4179
|
}
|
|
4008
4180
|
const encodeFrom = job.progress;
|
|
@@ -4039,7 +4211,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4039
4211
|
|
|
4040
4212
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
4041
4213
|
function segmentOutputPath(segmentDir, index) {
|
|
4042
|
-
return
|
|
4214
|
+
return join10(segmentDir, `segment_${String(index).padStart(5, "0")}.mp4`);
|
|
4043
4215
|
}
|
|
4044
4216
|
function segmentEncoderOptions(base, segment) {
|
|
4045
4217
|
return {
|
|
@@ -4115,7 +4287,7 @@ async function captureSegment(ctx, segment) {
|
|
|
4115
4287
|
path: segmentPath,
|
|
4116
4288
|
// Recorded so resume can prove the file on disk is the one that
|
|
4117
4289
|
// finished; absent only in unit tests, whose fake encoder writes none.
|
|
4118
|
-
bytes: existsSync6(segmentPath) ?
|
|
4290
|
+
bytes: existsSync6(segmentPath) ? statSync3(segmentPath).size : 0
|
|
4119
4291
|
});
|
|
4120
4292
|
return encodeResult.durationMs;
|
|
4121
4293
|
} finally {
|
|
@@ -4133,7 +4305,7 @@ var DEFAULT_DEPS = {
|
|
|
4133
4305
|
// is invisible to a test that swaps the engine module after this file has
|
|
4134
4306
|
// been evaluated (bun updates live bindings, not copies).
|
|
4135
4307
|
closeSession: (session) => closeCaptureSession2(session),
|
|
4136
|
-
removeFile: (path) =>
|
|
4308
|
+
removeFile: (path) => rmSync6(path, { force: true })
|
|
4137
4309
|
};
|
|
4138
4310
|
function defaultSessionFactory(input) {
|
|
4139
4311
|
let probeSession = input.probeSession;
|
|
@@ -4162,7 +4334,7 @@ function openSegmentedSessionReuse(input, session) {
|
|
|
4162
4334
|
async function openSegmentedSession(input) {
|
|
4163
4335
|
const captureCfg = input.cfg.forceScreenshot === input.plan.forceScreenshot ? input.cfg : { ...input.cfg, forceScreenshot: input.plan.forceScreenshot };
|
|
4164
4336
|
const videoInjector = input.createRenderVideoFrameInjector();
|
|
4165
|
-
const captureOptions = input.buildCaptureOptions();
|
|
4337
|
+
const captureOptions = { ...input.buildCaptureOptions(), deFrameFallback: false };
|
|
4166
4338
|
const ownBrowser = shouldDisableBrowserPoolForParallelWorker({
|
|
4167
4339
|
parallel: (input.workerCount ?? 1) > 1
|
|
4168
4340
|
});
|
|
@@ -4300,7 +4472,7 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4300
4472
|
const { log, dedupPerfs, assertNotAborted } = input;
|
|
4301
4473
|
const deps = { ...DEFAULT_DEPS, ...input.deps };
|
|
4302
4474
|
const segments = planSegments(input.totalFrames, input.segmentFrames);
|
|
4303
|
-
const segmentDir = input.segmentDir ??
|
|
4475
|
+
const segmentDir = input.segmentDir ?? join10(input.workDir, "segments");
|
|
4304
4476
|
mkdirSync3(segmentDir, { recursive: true });
|
|
4305
4477
|
const skip = input.completedSegments ?? /* @__PURE__ */ new Set();
|
|
4306
4478
|
const pending = segments.filter((s) => !skip.has(s.index));
|
|
@@ -4371,10 +4543,83 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4371
4543
|
};
|
|
4372
4544
|
}
|
|
4373
4545
|
|
|
4546
|
+
// src/services/render/renderEtaGuard.ts
|
|
4547
|
+
var RENDER_TOO_SLOW = "RENDER_TOO_SLOW";
|
|
4548
|
+
var ETA_MARGIN = 1.2;
|
|
4549
|
+
var MIN_SAMPLE_MS = 6e4;
|
|
4550
|
+
var MIN_SAMPLE_FRAMES = 3;
|
|
4551
|
+
var POLL_MS = 5e3;
|
|
4552
|
+
var RenderTooSlowError = class extends Error {
|
|
4553
|
+
code = RENDER_TOO_SLOW;
|
|
4554
|
+
// The producer server reads these off the error for the wire envelope.
|
|
4555
|
+
// fallow-ignore-next-line unused-class-member
|
|
4556
|
+
owner = "user";
|
|
4557
|
+
// fallow-ignore-next-line unused-class-member
|
|
4558
|
+
retryable = false;
|
|
4559
|
+
publicMetadata;
|
|
4560
|
+
constructor(stage, input) {
|
|
4561
|
+
const { framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs } = input;
|
|
4562
|
+
super(
|
|
4563
|
+
`render too slow: stage ${stage}, ${framesCompleted}/${totalFrames} frames, ${Math.round(msPerFrame)} ms/frame, ETA ${etaMs} ms exceeds maxRenderMs ${maxRenderMs}`
|
|
4564
|
+
);
|
|
4565
|
+
this.name = "RenderTooSlowError";
|
|
4566
|
+
this.publicMetadata = { stage, framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs };
|
|
4567
|
+
}
|
|
4568
|
+
};
|
|
4569
|
+
function checkRenderEta(stage, input) {
|
|
4570
|
+
const { maxRenderMs, elapsedMs, framesCompleted, totalFrames, msPerFrame } = input;
|
|
4571
|
+
if (!maxRenderMs || maxRenderMs <= 0 || !(msPerFrame > 0)) return void 0;
|
|
4572
|
+
const etaMs = Math.round(elapsedMs + (totalFrames - framesCompleted) * msPerFrame);
|
|
4573
|
+
if (etaMs <= maxRenderMs * ETA_MARGIN) return void 0;
|
|
4574
|
+
return new RenderTooSlowError(stage, { ...input, maxRenderMs, etaMs });
|
|
4575
|
+
}
|
|
4576
|
+
function createRateSampler() {
|
|
4577
|
+
let base;
|
|
4578
|
+
let lastFrames = 0;
|
|
4579
|
+
return {
|
|
4580
|
+
observe(done, at) {
|
|
4581
|
+
if (done === 0 || done < lastFrames) base = void 0;
|
|
4582
|
+
lastFrames = done;
|
|
4583
|
+
if (done === 0) return void 0;
|
|
4584
|
+
base ??= { at, frames: done };
|
|
4585
|
+
const sampleMs = at - base.at;
|
|
4586
|
+
const sampleFrames = done - base.frames;
|
|
4587
|
+
if (sampleMs < MIN_SAMPLE_MS || sampleFrames < MIN_SAMPLE_FRAMES) return void 0;
|
|
4588
|
+
return sampleMs / sampleFrames;
|
|
4589
|
+
}
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
function startRenderEtaGuard(options) {
|
|
4593
|
+
const { maxRenderMs, job, totalFrames, pipelineStartMs, onTooSlow, now = Date.now } = options;
|
|
4594
|
+
if (!maxRenderMs || maxRenderMs <= 0) return () => {
|
|
4595
|
+
};
|
|
4596
|
+
const sampler = createRateSampler();
|
|
4597
|
+
const stop = () => clearInterval(timer);
|
|
4598
|
+
const timer = setInterval(() => {
|
|
4599
|
+
const done = job.framesRendered ?? 0;
|
|
4600
|
+
if (done >= totalFrames) return stop();
|
|
4601
|
+
const at = now();
|
|
4602
|
+
const msPerFrame = sampler.observe(done, at);
|
|
4603
|
+
if (msPerFrame === void 0) return;
|
|
4604
|
+
const error = checkRenderEta("capture", {
|
|
4605
|
+
maxRenderMs,
|
|
4606
|
+
elapsedMs: at - pipelineStartMs,
|
|
4607
|
+
framesCompleted: done,
|
|
4608
|
+
totalFrames,
|
|
4609
|
+
msPerFrame
|
|
4610
|
+
});
|
|
4611
|
+
if (!error) return;
|
|
4612
|
+
stop();
|
|
4613
|
+
onTooSlow(error);
|
|
4614
|
+
}, options.pollMs ?? POLL_MS);
|
|
4615
|
+
timer.unref?.();
|
|
4616
|
+
return stop;
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4374
4619
|
// src/services/render/segmentManifest.ts
|
|
4375
4620
|
import { createHash as createHash2 } from "node:crypto";
|
|
4376
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as
|
|
4377
|
-
import { join as
|
|
4621
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4622
|
+
import { join as join11 } from "node:path";
|
|
4378
4623
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
4379
4624
|
var MANIFEST_FILENAME = "segments.json";
|
|
4380
4625
|
function computeSegmentPlanHash(input) {
|
|
@@ -4400,7 +4645,7 @@ function computeSegmentPlanHash(input) {
|
|
|
4400
4645
|
return createHash2("sha256").update(ordered).digest("hex").slice(0, 16);
|
|
4401
4646
|
}
|
|
4402
4647
|
function segmentDirFor(projectRendersDir, planHash) {
|
|
4403
|
-
return
|
|
4648
|
+
return join11(projectRendersDir, ".hf-segments", planHash);
|
|
4404
4649
|
}
|
|
4405
4650
|
function hasNumber(value, key) {
|
|
4406
4651
|
return key in value && typeof Reflect.get(value, key) === "number";
|
|
@@ -4418,7 +4663,7 @@ function isManifest(value) {
|
|
|
4418
4663
|
return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
|
|
4419
4664
|
}
|
|
4420
4665
|
function readSegmentManifest(segmentDir) {
|
|
4421
|
-
const path =
|
|
4666
|
+
const path = join11(segmentDir, MANIFEST_FILENAME);
|
|
4422
4667
|
if (!existsSync7(path)) return null;
|
|
4423
4668
|
try {
|
|
4424
4669
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -4429,9 +4674,9 @@ function readSegmentManifest(segmentDir) {
|
|
|
4429
4674
|
}
|
|
4430
4675
|
function writeSegmentManifest(segmentDir, manifest) {
|
|
4431
4676
|
mkdirSync4(segmentDir, { recursive: true });
|
|
4432
|
-
const tmp =
|
|
4677
|
+
const tmp = join11(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
|
|
4433
4678
|
writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
4434
|
-
renameWithRetry2(tmp,
|
|
4679
|
+
renameWithRetry2(tmp, join11(segmentDir, MANIFEST_FILENAME));
|
|
4435
4680
|
}
|
|
4436
4681
|
async function probeSegmentFrameCount(path, probe) {
|
|
4437
4682
|
try {
|
|
@@ -4449,7 +4694,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
4449
4694
|
if (manifest.planHash !== expectedHash) return ok;
|
|
4450
4695
|
for (const entry of manifest.completed) {
|
|
4451
4696
|
if (!existsSync7(entry.path)) continue;
|
|
4452
|
-
const size =
|
|
4697
|
+
const size = statSync4(entry.path).size;
|
|
4453
4698
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
4454
4699
|
const frames = await probeFrames(entry.path);
|
|
4455
4700
|
if (frames !== entry.endFrame - entry.startFrame) continue;
|
|
@@ -4543,7 +4788,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
|
|
|
4543
4788
|
}
|
|
4544
4789
|
|
|
4545
4790
|
// src/services/render/captureCost.ts
|
|
4546
|
-
import { join as
|
|
4791
|
+
import { join as join12 } from "node:path";
|
|
4547
4792
|
import { fpsToNumber as fpsToNumber5 } from "@hyperframes/core";
|
|
4548
4793
|
import {
|
|
4549
4794
|
calculateOptimalWorkers,
|
|
@@ -4788,7 +5033,7 @@ async function runCaptureCalibration(input) {
|
|
|
4788
5033
|
});
|
|
4789
5034
|
let calibration;
|
|
4790
5035
|
try {
|
|
4791
|
-
calibration = await runOneCalibration(
|
|
5036
|
+
calibration = await runOneCalibration(join12(workDir, "capture-calibration"), calibrationCfg);
|
|
4792
5037
|
} catch (error) {
|
|
4793
5038
|
const shouldFallback = !forceScreenshot && shouldFallbackToScreenshotAfterCalibrationError(error);
|
|
4794
5039
|
if (!shouldFallback) {
|
|
@@ -4821,7 +5066,7 @@ async function runCaptureCalibration(input) {
|
|
|
4821
5066
|
const screenshotCfg = createCaptureCalibrationConfig({ ...cfg, forceScreenshot: true });
|
|
4822
5067
|
try {
|
|
4823
5068
|
calibration = await runOneCalibration(
|
|
4824
|
-
|
|
5069
|
+
join12(workDir, "capture-calibration-screenshot"),
|
|
4825
5070
|
screenshotCfg
|
|
4826
5071
|
);
|
|
4827
5072
|
} catch (fallbackError) {
|
|
@@ -5253,14 +5498,14 @@ function countAuthoredTimedClips(html) {
|
|
|
5253
5498
|
}
|
|
5254
5499
|
|
|
5255
5500
|
// src/services/render/stages/compileStage.ts
|
|
5256
|
-
import { join as
|
|
5501
|
+
import { join as join15 } from "node:path";
|
|
5257
5502
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5258
5503
|
|
|
5259
5504
|
// src/services/htmlCompiler.ts
|
|
5260
5505
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5261
5506
|
import { createHash as createHash5 } from "node:crypto";
|
|
5262
5507
|
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
|
|
5263
|
-
import { join as
|
|
5508
|
+
import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
|
|
5264
5509
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
5265
5510
|
import {
|
|
5266
5511
|
COMPOSITION_SOURCE_URL,
|
|
@@ -5487,8 +5732,8 @@ import {
|
|
|
5487
5732
|
|
|
5488
5733
|
// src/services/animatedGifPrep.ts
|
|
5489
5734
|
import { createHash as createHash4 } from "node:crypto";
|
|
5490
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as
|
|
5491
|
-
import { dirname as dirname5, isAbsolute as isAbsolute2, join as
|
|
5735
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
|
|
5736
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join13, resolve as resolve5 } from "node:path";
|
|
5492
5737
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
5493
5738
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
5494
5739
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -5537,7 +5782,7 @@ function resolveGifSourcePath(src, options) {
|
|
|
5537
5782
|
}
|
|
5538
5783
|
function isUsableFile(path) {
|
|
5539
5784
|
try {
|
|
5540
|
-
const stat =
|
|
5785
|
+
const stat = statSync5(path);
|
|
5541
5786
|
return stat.isFile() && stat.size > 0;
|
|
5542
5787
|
} catch {
|
|
5543
5788
|
return false;
|
|
@@ -5672,10 +5917,10 @@ async function ensurePreparedWebm(input) {
|
|
|
5672
5917
|
if (!isUsableFile(input.cachePath)) {
|
|
5673
5918
|
renameWithRetry3(tmpPath, input.cachePath);
|
|
5674
5919
|
} else {
|
|
5675
|
-
|
|
5920
|
+
rmSync7(tmpPath, { force: true });
|
|
5676
5921
|
}
|
|
5677
5922
|
} catch (error) {
|
|
5678
|
-
|
|
5923
|
+
rmSync7(tmpPath, { force: true });
|
|
5679
5924
|
throw error;
|
|
5680
5925
|
}
|
|
5681
5926
|
}
|
|
@@ -5731,7 +5976,7 @@ function replaceImageWithVideo(input) {
|
|
|
5731
5976
|
return video;
|
|
5732
5977
|
}
|
|
5733
5978
|
async function prepareAnimatedGifInputs(html, options) {
|
|
5734
|
-
const outputDir = options.outputDir ??
|
|
5979
|
+
const outputDir = options.outputDir ?? join13(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
5735
5980
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
5736
5981
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
5737
5982
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -5753,8 +5998,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
5753
5998
|
const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
|
|
5754
5999
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
5755
6000
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
5756
|
-
const cachePath =
|
|
5757
|
-
const outputPath =
|
|
6001
|
+
const cachePath = join13(cacheDir, filename);
|
|
6002
|
+
const outputPath = join13(outputDir, filename);
|
|
5758
6003
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
5759
6004
|
await ensurePreparedWebm({
|
|
5760
6005
|
sourcePath,
|
|
@@ -6105,10 +6350,6 @@ var THREE_D_CONTEXT_PATTERN = /transform-style\s*:\s*preserve-3d|backface-visibi
|
|
|
6105
6350
|
function detectThreeDTransformUsage(html) {
|
|
6106
6351
|
return THREE_D_CONTEXT_PATTERN.test(html);
|
|
6107
6352
|
}
|
|
6108
|
-
var MIX_BLEND_MODE_PATTERN = /mix-blend-mode\s*:/i;
|
|
6109
|
-
function detectMixBlendModeUsage(html) {
|
|
6110
|
-
return MIX_BLEND_MODE_PATTERN.test(html);
|
|
6111
|
-
}
|
|
6112
6353
|
var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
|
|
6113
6354
|
function detectAncestorBackgroundImage(html) {
|
|
6114
6355
|
const { document: document2 } = parseHTML4(html);
|
|
@@ -6168,7 +6409,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6168
6409
|
}
|
|
6169
6410
|
} else {
|
|
6170
6411
|
filePath = decodedUrlPath(src);
|
|
6171
|
-
if (!filePath.startsWith("/")) filePath =
|
|
6412
|
+
if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
|
|
6172
6413
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
6173
6414
|
}
|
|
6174
6415
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -6788,7 +7029,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
6788
7029
|
return downloadAndRewriteUrls(
|
|
6789
7030
|
urlSet,
|
|
6790
7031
|
html,
|
|
6791
|
-
|
|
7032
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6792
7033
|
"Remote media download failed",
|
|
6793
7034
|
"Localized remote media source(s)"
|
|
6794
7035
|
);
|
|
@@ -6803,7 +7044,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
6803
7044
|
return downloadAndRewriteUrls(
|
|
6804
7045
|
urlSet,
|
|
6805
7046
|
html,
|
|
6806
|
-
|
|
7047
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6807
7048
|
"Remote image download failed",
|
|
6808
7049
|
"Localized remote image source(s)"
|
|
6809
7050
|
);
|
|
@@ -6819,7 +7060,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
6819
7060
|
return downloadAndRewriteUrls(
|
|
6820
7061
|
urlSet,
|
|
6821
7062
|
html,
|
|
6822
|
-
|
|
7063
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6823
7064
|
"Remote background-image download failed",
|
|
6824
7065
|
"Localized remote background-image(s)",
|
|
6825
7066
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -6954,7 +7195,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
6954
7195
|
return downloadAndRewriteUrls(
|
|
6955
7196
|
urlSet,
|
|
6956
7197
|
processed,
|
|
6957
|
-
|
|
7198
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6958
7199
|
"Remote font download failed",
|
|
6959
7200
|
"Localized remote font face(s)",
|
|
6960
7201
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -7121,7 +7362,6 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7121
7362
|
const renderModeHints = detectRenderModeHints(sanitizedHtml, options.pageMotionBlur);
|
|
7122
7363
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
7123
7364
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
7124
|
-
const usesMixBlendMode = detectMixBlendModeUsage(sanitizedHtml);
|
|
7125
7365
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
7126
7366
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
7127
7367
|
injectTextRenderingRule(
|
|
@@ -7235,7 +7475,6 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7235
7475
|
renderModeHints,
|
|
7236
7476
|
hasShaderTransitions,
|
|
7237
7477
|
usesThreeDTransforms,
|
|
7238
|
-
usesMixBlendMode,
|
|
7239
7478
|
hasAncestorBackgroundImage
|
|
7240
7479
|
};
|
|
7241
7480
|
}
|
|
@@ -7621,7 +7860,7 @@ async function runCompileStage(input) {
|
|
|
7621
7860
|
abortSignal
|
|
7622
7861
|
} = input;
|
|
7623
7862
|
const compileStart = Date.now();
|
|
7624
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
7863
|
+
const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
|
|
7625
7864
|
log,
|
|
7626
7865
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
7627
7866
|
allowSystemFontCapture,
|
|
@@ -7629,7 +7868,7 @@ async function runCompileStage(input) {
|
|
|
7629
7868
|
variables: input.variables,
|
|
7630
7869
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7631
7870
|
pageMotionBlur: input.pageMotionBlur,
|
|
7632
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
7871
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7633
7872
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7634
7873
|
});
|
|
7635
7874
|
assertNotAborted();
|
|
@@ -7642,13 +7881,6 @@ async function runCompileStage(input) {
|
|
|
7642
7881
|
"[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility), so disabling drawElementImage for this render. Capture uses the platform's baseline route."
|
|
7643
7882
|
);
|
|
7644
7883
|
}
|
|
7645
|
-
if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_BLEND !== "true" && compiled.usesMixBlendMode) {
|
|
7646
|
-
cfg.useDrawElement = false;
|
|
7647
|
-
deCompileGate = "mix_blend_mode";
|
|
7648
|
-
log.info(
|
|
7649
|
-
"[Render] Fast capture: composition uses mix-blend-mode, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
|
|
7650
|
-
);
|
|
7651
|
-
}
|
|
7652
7884
|
if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_ANCESTOR_BG !== "true" && compiled.hasAncestorBackgroundImage) {
|
|
7653
7885
|
cfg.useDrawElement = false;
|
|
7654
7886
|
deCompileGate = "ancestor_background_image";
|
|
@@ -7692,7 +7924,7 @@ async function runCompileStage(input) {
|
|
|
7692
7924
|
};
|
|
7693
7925
|
await preflightCompositionAssetMediaTypes({
|
|
7694
7926
|
projectDir,
|
|
7695
|
-
compiledDir:
|
|
7927
|
+
compiledDir: join15(workDir, "compiled"),
|
|
7696
7928
|
composition,
|
|
7697
7929
|
signal: abortSignal
|
|
7698
7930
|
});
|
|
@@ -7737,7 +7969,7 @@ async function runCompileStage(input) {
|
|
|
7737
7969
|
}
|
|
7738
7970
|
|
|
7739
7971
|
// src/services/render/stages/probeStage.ts
|
|
7740
|
-
import { join as
|
|
7972
|
+
import { join as join16 } from "node:path";
|
|
7741
7973
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
7742
7974
|
import {
|
|
7743
7975
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -7929,7 +8161,7 @@ async function runProbeStage(input) {
|
|
|
7929
8161
|
});
|
|
7930
8162
|
fileServer = await createFileServer({
|
|
7931
8163
|
projectDir,
|
|
7932
|
-
compiledDir:
|
|
8164
|
+
compiledDir: join16(workDir, "compiled"),
|
|
7933
8165
|
port: 0,
|
|
7934
8166
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
7935
8167
|
fps: job.config.fps
|
|
@@ -7957,7 +8189,7 @@ async function runProbeStage(input) {
|
|
|
7957
8189
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
7958
8190
|
probeSession = await createCaptureSession4(
|
|
7959
8191
|
fileServer.url,
|
|
7960
|
-
|
|
8192
|
+
join16(workDir, "probe"),
|
|
7961
8193
|
captureOpts,
|
|
7962
8194
|
null,
|
|
7963
8195
|
probeCfg
|
|
@@ -8042,7 +8274,7 @@ async function runProbeStage(input) {
|
|
|
8042
8274
|
});
|
|
8043
8275
|
probeSession = await createCaptureSession4(
|
|
8044
8276
|
fileServer.url,
|
|
8045
|
-
|
|
8277
|
+
join16(workDir, "probe-screenshot"),
|
|
8046
8278
|
captureOpts,
|
|
8047
8279
|
null,
|
|
8048
8280
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -8078,7 +8310,7 @@ async function runProbeStage(input) {
|
|
|
8078
8310
|
compiled,
|
|
8079
8311
|
resolutions,
|
|
8080
8312
|
projectDir,
|
|
8081
|
-
|
|
8313
|
+
join16(workDir, "downloads")
|
|
8082
8314
|
);
|
|
8083
8315
|
assertNotAborted();
|
|
8084
8316
|
composition.videos = compiled.videos;
|
|
@@ -8269,7 +8501,7 @@ async function runProbeStage(input) {
|
|
|
8269
8501
|
}
|
|
8270
8502
|
await preflightCompositionAssetMediaTypes({
|
|
8271
8503
|
projectDir,
|
|
8272
|
-
compiledDir:
|
|
8504
|
+
compiledDir: join16(workDir, "compiled"),
|
|
8273
8505
|
composition,
|
|
8274
8506
|
signal: abortSignal
|
|
8275
8507
|
});
|
|
@@ -8434,7 +8666,7 @@ function validateDistributedDuration(input) {
|
|
|
8434
8666
|
|
|
8435
8667
|
// src/services/render/stages/extractVideosStage.ts
|
|
8436
8668
|
import { existsSync as existsSync11 } from "node:fs";
|
|
8437
|
-
import { join as
|
|
8669
|
+
import { join as join17 } from "node:path";
|
|
8438
8670
|
import {
|
|
8439
8671
|
classifyVideoExtractionError,
|
|
8440
8672
|
createFrameLookupTable,
|
|
@@ -8754,14 +8986,14 @@ async function runExtractVideosStage(input) {
|
|
|
8754
8986
|
// because short boundary counts can differ by one frame.
|
|
8755
8987
|
{
|
|
8756
8988
|
fps: job.config.fps,
|
|
8757
|
-
outputDir:
|
|
8989
|
+
outputDir: join17(compiledDir, "__hyperframes_video_frames"),
|
|
8758
8990
|
format: job.config.videoFrameFormat ?? "auto",
|
|
8759
8991
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
8760
8992
|
timelineEnd: composition.duration,
|
|
8761
8993
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
8762
8994
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
8763
8995
|
deferRangeExtraction,
|
|
8764
|
-
contentKeyedDirs: [
|
|
8996
|
+
contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
8765
8997
|
urlByAbsentLocalPath
|
|
8766
8998
|
},
|
|
8767
8999
|
abortSignal,
|
|
@@ -8828,8 +9060,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8828
9060
|
}
|
|
8829
9061
|
|
|
8830
9062
|
// src/services/render/stages/audioStage.ts
|
|
8831
|
-
import { join as
|
|
8832
|
-
import { rmSync as
|
|
9063
|
+
import { join as join19 } from "node:path";
|
|
9064
|
+
import { rmSync as rmSync9 } from "node:fs";
|
|
8833
9065
|
import {
|
|
8834
9066
|
MIXED_AUDIO_FILENAME,
|
|
8835
9067
|
audioFailureDiagnostics,
|
|
@@ -8838,8 +9070,8 @@ import {
|
|
|
8838
9070
|
|
|
8839
9071
|
// src/services/render/audioPadTrim.ts
|
|
8840
9072
|
import { spawn } from "node:child_process";
|
|
8841
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
8842
|
-
import { dirname as dirname7, join as
|
|
9073
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
|
|
9074
|
+
import { dirname as dirname7, join as join18 } from "node:path";
|
|
8843
9075
|
import {
|
|
8844
9076
|
DEFAULT_CONFIG,
|
|
8845
9077
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -9042,7 +9274,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
9042
9274
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
9043
9275
|
};
|
|
9044
9276
|
} finally {
|
|
9045
|
-
for (const path of plan2.cleanupPaths)
|
|
9277
|
+
for (const path of plan2.cleanupPaths) rmSync8(path, { force: true });
|
|
9046
9278
|
}
|
|
9047
9279
|
let loweredDb = 0;
|
|
9048
9280
|
if (probeTruePeak) {
|
|
@@ -9079,19 +9311,21 @@ async function constrainAacTruePeak(input) {
|
|
|
9079
9311
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
9080
9312
|
return enforceAacTruePeak({
|
|
9081
9313
|
audioPath: input.audioPath,
|
|
9314
|
+
sourcePath: input.sourcePath,
|
|
9082
9315
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
9083
9316
|
signal: input.signal,
|
|
9084
|
-
probeTruePeak: (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }),
|
|
9085
|
-
encode: defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
9317
|
+
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
9318
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
9086
9319
|
});
|
|
9087
9320
|
}
|
|
9088
9321
|
async function enforceAacTruePeak(input) {
|
|
9089
9322
|
let scratchDir;
|
|
9090
9323
|
try {
|
|
9091
|
-
scratchDir = mkdtempSync3(
|
|
9092
|
-
const correctedPath =
|
|
9324
|
+
scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
|
|
9325
|
+
const correctedPath = join18(scratchDir, "audio.m4a");
|
|
9093
9326
|
const measurements = [];
|
|
9094
9327
|
let attenuationDb = 0;
|
|
9328
|
+
let sourcePeakDbfs;
|
|
9095
9329
|
let measuredPath = input.audioPath;
|
|
9096
9330
|
for (let pass = 0; pass <= MAX_TRUE_PEAK_CORRECTION_PASSES; pass += 1) {
|
|
9097
9331
|
const truePeakDbfs = await input.probeTruePeak(measuredPath, input.signal);
|
|
@@ -9108,7 +9342,11 @@ async function enforceAacTruePeak(input) {
|
|
|
9108
9342
|
}
|
|
9109
9343
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
9110
9344
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
9111
|
-
|
|
9345
|
+
if (input.sourcePath && sourcePeakDbfs === void 0) {
|
|
9346
|
+
sourcePeakDbfs = await input.probeTruePeak(input.sourcePath, input.signal);
|
|
9347
|
+
}
|
|
9348
|
+
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
9349
|
+
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
9112
9350
|
const result = await input.encode(
|
|
9113
9351
|
input.audioPath,
|
|
9114
9352
|
correctedPath,
|
|
@@ -9140,7 +9378,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9140
9378
|
)}`
|
|
9141
9379
|
};
|
|
9142
9380
|
} finally {
|
|
9143
|
-
if (scratchDir)
|
|
9381
|
+
if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
|
|
9144
9382
|
}
|
|
9145
9383
|
}
|
|
9146
9384
|
function failResult(outputPath, target, source, error) {
|
|
@@ -9287,7 +9525,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
9287
9525
|
}
|
|
9288
9526
|
async function runAudioStage(input) {
|
|
9289
9527
|
const stageStart = Date.now();
|
|
9290
|
-
const pcmSourcePath =
|
|
9528
|
+
const pcmSourcePath = join19(input.workDir, "audio-source.wav");
|
|
9291
9529
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
9292
9530
|
try {
|
|
9293
9531
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -9307,7 +9545,7 @@ async function runAudioStage(input) {
|
|
|
9307
9545
|
}
|
|
9308
9546
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9309
9547
|
} finally {
|
|
9310
|
-
|
|
9548
|
+
rmSync9(pcmSourcePath, { force: true });
|
|
9311
9549
|
}
|
|
9312
9550
|
}
|
|
9313
9551
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -9325,7 +9563,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9325
9563
|
assertNotAborted,
|
|
9326
9564
|
log
|
|
9327
9565
|
} = input;
|
|
9328
|
-
const audioOutputPath =
|
|
9566
|
+
const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
|
|
9329
9567
|
let hasAudio = false;
|
|
9330
9568
|
let audioError;
|
|
9331
9569
|
let audioFailures;
|
|
@@ -9336,7 +9574,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9336
9574
|
audioResult = await processCompositionAudio(
|
|
9337
9575
|
audios,
|
|
9338
9576
|
projectDir,
|
|
9339
|
-
|
|
9577
|
+
join19(workDir, "audio-work"),
|
|
9340
9578
|
audioOutputPath,
|
|
9341
9579
|
duration,
|
|
9342
9580
|
abortSignal,
|
|
@@ -9405,7 +9643,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9405
9643
|
|
|
9406
9644
|
// src/services/render/stages/captureStage.ts
|
|
9407
9645
|
import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
9408
|
-
import { join as
|
|
9646
|
+
import { join as join20 } from "node:path";
|
|
9409
9647
|
import {
|
|
9410
9648
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
9411
9649
|
frameFileExtension,
|
|
@@ -9478,13 +9716,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
9478
9716
|
if (!existsSync12(workDir)) return total;
|
|
9479
9717
|
for (const attempt of readdirSync3(workDir)) {
|
|
9480
9718
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
9481
|
-
total += sampleDirectoryBytes(
|
|
9719
|
+
total += sampleDirectoryBytes(join20(workDir, attempt));
|
|
9482
9720
|
continue;
|
|
9483
9721
|
}
|
|
9484
9722
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
9485
|
-
const attemptDir =
|
|
9723
|
+
const attemptDir = join20(workDir, attempt);
|
|
9486
9724
|
for (const name of readdirSync3(attemptDir)) {
|
|
9487
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
9725
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
|
|
9488
9726
|
}
|
|
9489
9727
|
}
|
|
9490
9728
|
return total;
|
|
@@ -9544,7 +9782,7 @@ async function runCaptureStage(input) {
|
|
|
9544
9782
|
);
|
|
9545
9783
|
}
|
|
9546
9784
|
}
|
|
9547
|
-
const captureOptions = buildCaptureOptions();
|
|
9785
|
+
const captureOptions = { ...buildCaptureOptions(), deFrameFallback: false };
|
|
9548
9786
|
const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
|
|
9549
9787
|
const checkDiskProjection = createDiskCaptureProjection({
|
|
9550
9788
|
framesDir,
|
|
@@ -9771,7 +10009,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
9771
10009
|
|
|
9772
10010
|
// src/services/render/stages/captureHdrStage.ts
|
|
9773
10011
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
|
|
9774
|
-
import { join as
|
|
10012
|
+
import { join as join24 } from "node:path";
|
|
9775
10013
|
import {
|
|
9776
10014
|
closeCaptureSession as closeCaptureSession7,
|
|
9777
10015
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -9843,7 +10081,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
9843
10081
|
}
|
|
9844
10082
|
|
|
9845
10083
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
9846
|
-
import { join as
|
|
10084
|
+
import { join as join21 } from "node:path";
|
|
9847
10085
|
import {
|
|
9848
10086
|
TRANSITIONS,
|
|
9849
10087
|
crossfade
|
|
@@ -9968,7 +10206,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
9968
10206
|
);
|
|
9969
10207
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
9970
10208
|
writeFileExclusiveSync(
|
|
9971
|
-
|
|
10209
|
+
join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
9972
10210
|
normalCanvas
|
|
9973
10211
|
);
|
|
9974
10212
|
}
|
|
@@ -10000,7 +10238,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10000
10238
|
}
|
|
10001
10239
|
|
|
10002
10240
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
10003
|
-
import { join as
|
|
10241
|
+
import { join as join23 } from "node:path";
|
|
10004
10242
|
import {
|
|
10005
10243
|
TRANSITIONS as TRANSITIONS2,
|
|
10006
10244
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -10014,7 +10252,7 @@ import {
|
|
|
10014
10252
|
// src/services/shaderTransitionWorkerPool.ts
|
|
10015
10253
|
import { Worker } from "node:worker_threads";
|
|
10016
10254
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
10017
|
-
import { dirname as dirname8, join as
|
|
10255
|
+
import { dirname as dirname8, join as join22 } from "node:path";
|
|
10018
10256
|
import { createRequire } from "node:module";
|
|
10019
10257
|
import { existsSync as existsSync13 } from "node:fs";
|
|
10020
10258
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -10028,9 +10266,9 @@ function resolveWorkerEntry(explicit) {
|
|
|
10028
10266
|
return { path: override, isTs };
|
|
10029
10267
|
}
|
|
10030
10268
|
const moduleDir = dirname8(fileURLToPath3(import.meta.url));
|
|
10031
|
-
const jsPath =
|
|
10269
|
+
const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
|
|
10032
10270
|
if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
|
|
10033
|
-
const tsPath =
|
|
10271
|
+
const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
|
|
10034
10272
|
return { path: tsPath, isTs: true };
|
|
10035
10273
|
}
|
|
10036
10274
|
function buildExecArgv(entryIsTs) {
|
|
@@ -10398,7 +10636,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
10398
10636
|
);
|
|
10399
10637
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10400
10638
|
writeFileExclusiveSync(
|
|
10401
|
-
|
|
10639
|
+
join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10402
10640
|
canvas
|
|
10403
10641
|
);
|
|
10404
10642
|
}
|
|
@@ -10567,7 +10805,8 @@ async function runCaptureHdrStage(input) {
|
|
|
10567
10805
|
width,
|
|
10568
10806
|
height,
|
|
10569
10807
|
abortSignal,
|
|
10570
|
-
hdrDiagnostics
|
|
10808
|
+
hdrDiagnostics,
|
|
10809
|
+
maxResidentWindows: Math.max(1, workerCount) + 1
|
|
10571
10810
|
});
|
|
10572
10811
|
releaseHdrExtractionReservation = extracted.releaseReservation;
|
|
10573
10812
|
for (const [id, source] of extracted.sources) hdrVideoFrameSources.set(id, source);
|
|
@@ -10585,7 +10824,7 @@ async function runCaptureHdrStage(input) {
|
|
|
10585
10824
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
10586
10825
|
}
|
|
10587
10826
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
10588
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
10827
|
+
const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
|
|
10589
10828
|
if (debugDumpDir && !existsSync14(debugDumpDir)) {
|
|
10590
10829
|
mkdirSync7(debugDumpDir, { recursive: true });
|
|
10591
10830
|
}
|
|
@@ -10745,12 +10984,12 @@ import {
|
|
|
10745
10984
|
mkdirSync as mkdirSync8,
|
|
10746
10985
|
readdirSync as readdirSync4,
|
|
10747
10986
|
readFileSync as readFileSync7,
|
|
10748
|
-
rmSync as
|
|
10749
|
-
statSync as
|
|
10987
|
+
rmSync as rmSync10,
|
|
10988
|
+
statSync as statSync6,
|
|
10750
10989
|
writeFileSync as writeFileSync4
|
|
10751
10990
|
} from "node:fs";
|
|
10752
10991
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
10753
|
-
import { dirname as dirname9, join as
|
|
10992
|
+
import { dirname as dirname9, join as join25 } from "node:path";
|
|
10754
10993
|
import {
|
|
10755
10994
|
encodeFramesChunkedConcat,
|
|
10756
10995
|
encodeFramesFromDir,
|
|
@@ -10875,7 +11114,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
10875
11114
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
10876
11115
|
if (failure) return failure;
|
|
10877
11116
|
}
|
|
10878
|
-
const fileSize = existsSync15(outputPath) ?
|
|
11117
|
+
const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
|
|
10879
11118
|
return {
|
|
10880
11119
|
success: true,
|
|
10881
11120
|
outputPath,
|
|
@@ -10884,7 +11123,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
10884
11123
|
fileSize
|
|
10885
11124
|
};
|
|
10886
11125
|
} finally {
|
|
10887
|
-
|
|
11126
|
+
rmSync10(input.palettePath, { force: true });
|
|
10888
11127
|
}
|
|
10889
11128
|
}
|
|
10890
11129
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -10959,7 +11198,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
10959
11198
|
await new Promise((resolve12) => wake = resolve12);
|
|
10960
11199
|
}
|
|
10961
11200
|
if (signal.aborted) return;
|
|
10962
|
-
const mergedPath =
|
|
11201
|
+
const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
10963
11202
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
10964
11203
|
landed.delete(next);
|
|
10965
11204
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -11042,11 +11281,11 @@ async function runEncodeStage(input) {
|
|
|
11042
11281
|
);
|
|
11043
11282
|
}
|
|
11044
11283
|
captured.forEach((name, i) => {
|
|
11045
|
-
const dst =
|
|
11046
|
-
copyFileSync3(
|
|
11284
|
+
const dst = join25(outputPath, formatExportFrameName(i, "png"));
|
|
11285
|
+
copyFileSync3(join25(framesDir, name), dst);
|
|
11047
11286
|
});
|
|
11048
11287
|
if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
|
|
11049
|
-
copyFileSync3(audioOutputPath,
|
|
11288
|
+
copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
|
|
11050
11289
|
log.info(
|
|
11051
11290
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
11052
11291
|
);
|
|
@@ -11066,7 +11305,7 @@ async function runEncodeStage(input) {
|
|
|
11066
11305
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
11067
11306
|
fps: job.config.fps,
|
|
11068
11307
|
loop,
|
|
11069
|
-
palettePath:
|
|
11308
|
+
palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
|
|
11070
11309
|
preserveAlpha: needsAlpha,
|
|
11071
11310
|
signal: abortSignal,
|
|
11072
11311
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -11279,9 +11518,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
11279
11518
|
continue;
|
|
11280
11519
|
}
|
|
11281
11520
|
for (const name of entries) {
|
|
11282
|
-
const full =
|
|
11521
|
+
const full = join26(current, name);
|
|
11283
11522
|
try {
|
|
11284
|
-
const st =
|
|
11523
|
+
const st = statSync7(full);
|
|
11285
11524
|
if (st.isDirectory()) {
|
|
11286
11525
|
stack.push(full);
|
|
11287
11526
|
} else if (st.isFile()) {
|
|
@@ -11428,8 +11667,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11428
11667
|
const ranges = [];
|
|
11429
11668
|
let rangeStart = null;
|
|
11430
11669
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11431
|
-
const framePath =
|
|
11432
|
-
const missing = !existsSync16(framePath) ||
|
|
11670
|
+
const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11671
|
+
const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
|
|
11433
11672
|
if (missing && rangeStart === null) {
|
|
11434
11673
|
rangeStart = frameIndex;
|
|
11435
11674
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -11451,7 +11690,7 @@ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, ran
|
|
|
11451
11690
|
workerId,
|
|
11452
11691
|
startFrame: rangeStart + range.startFrame,
|
|
11453
11692
|
endFrame: rangeStart + range.endFrame,
|
|
11454
|
-
outputDir:
|
|
11693
|
+
outputDir: join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
|
|
11455
11694
|
outputFrameOffset: rangeStart
|
|
11456
11695
|
}));
|
|
11457
11696
|
batches.push(batch);
|
|
@@ -11479,8 +11718,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11479
11718
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11480
11719
|
}
|
|
11481
11720
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11482
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
11483
|
-
return
|
|
11721
|
+
if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
|
|
11722
|
+
return join26(dirname10(outputPath), `work-${jobId}-`);
|
|
11484
11723
|
}
|
|
11485
11724
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
11486
11725
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -11512,7 +11751,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
11512
11751
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
11513
11752
|
let captured = 0;
|
|
11514
11753
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11515
|
-
const framePath =
|
|
11754
|
+
const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11516
11755
|
if (existsSync16(framePath)) captured++;
|
|
11517
11756
|
}
|
|
11518
11757
|
return captured;
|
|
@@ -11538,7 +11777,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11538
11777
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
11539
11778
|
});
|
|
11540
11779
|
pendingTransientRetry = false;
|
|
11541
|
-
const attemptWorkDir =
|
|
11780
|
+
const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
|
|
11542
11781
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
11543
11782
|
missingRanges,
|
|
11544
11783
|
currentWorkers,
|
|
@@ -11995,7 +12234,10 @@ function resolveDeShortBand(args) {
|
|
|
11995
12234
|
return args.bandOpen ? "applied" : "skipped_elements";
|
|
11996
12235
|
}
|
|
11997
12236
|
function shouldPreferSingleWorkerDrawElement(args) {
|
|
11998
|
-
return args.workerCount > 1 &&
|
|
12237
|
+
return args.workerCount > 1 && args.useDrawElement && !args.deCompileGate && !args.forceScreenshot && outputUsesH264Pipeline(args.outputFormat) && args.minFrames > 0 && args.totalFrames >= args.minFrames && args.singleWorkerStreamingOk && !args.layeredOrEffectRoute && !args.supersampling && !args.probeDeGated && !args.experimentalParallelDeOptIn;
|
|
12238
|
+
}
|
|
12239
|
+
function probeNeedsScreenshotFrames(session) {
|
|
12240
|
+
return (session?.deRiskFrames?.size ?? 0) > 0;
|
|
11999
12241
|
}
|
|
12000
12242
|
function isProbeDeGated(session) {
|
|
12001
12243
|
return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
|
|
@@ -12131,17 +12373,17 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
12131
12373
|
}
|
|
12132
12374
|
function resolveRenderDebugDir() {
|
|
12133
12375
|
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
|
|
12134
|
-
return
|
|
12376
|
+
return join26(producerRoot, ".debug");
|
|
12135
12377
|
}
|
|
12136
12378
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
12137
12379
|
validateHlsRenderConfig(job.config);
|
|
12138
12380
|
const debugDir = resolveRenderDebugDir();
|
|
12139
12381
|
const outputDir = dirname10(outputPath);
|
|
12140
12382
|
if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
|
|
12141
|
-
const workDir = job.config.debug ?
|
|
12383
|
+
const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
12142
12384
|
const pipelineStart = Date.now();
|
|
12143
12385
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
12144
|
-
const logPath = job.config.debug ?
|
|
12386
|
+
const logPath = job.config.debug ? join26(workDir, "render.log") : null;
|
|
12145
12387
|
const execution = new RenderExecutionContext({
|
|
12146
12388
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
12147
12389
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -12155,7 +12397,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
12155
12397
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
12156
12398
|
return;
|
|
12157
12399
|
}
|
|
12158
|
-
|
|
12400
|
+
rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
12159
12401
|
});
|
|
12160
12402
|
try {
|
|
12161
12403
|
await executeRenderPipeline({
|
|
@@ -12186,7 +12428,9 @@ async function executeRenderPipeline(input) {
|
|
|
12186
12428
|
const log = execution.logger;
|
|
12187
12429
|
const eventPublisher = execution.events;
|
|
12188
12430
|
const onProgress = execution.onProgress;
|
|
12189
|
-
const
|
|
12431
|
+
const tooSlow = new AbortController();
|
|
12432
|
+
let tooSlowError;
|
|
12433
|
+
const executionSignal = job.config.maxRenderMs ? AbortSignal.any([tooSlow.signal, ...execution.signal ? [execution.signal] : []]) : execution.signal;
|
|
12190
12434
|
let fileServer = null;
|
|
12191
12435
|
let probeSession = null;
|
|
12192
12436
|
let lastBrowserConsole = [];
|
|
@@ -12199,7 +12443,7 @@ async function executeRenderPipeline(input) {
|
|
|
12199
12443
|
imageDecodeFailures: 0
|
|
12200
12444
|
};
|
|
12201
12445
|
let hdrPerf;
|
|
12202
|
-
const perfOutputPath =
|
|
12446
|
+
const perfOutputPath = join26(workDir, "perf-summary.json");
|
|
12203
12447
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
12204
12448
|
const observability = new RenderObservabilityRecorder({
|
|
12205
12449
|
pipelineStartMs: pipelineStart,
|
|
@@ -12301,15 +12545,15 @@ async function executeRenderPipeline(input) {
|
|
|
12301
12545
|
requestedWorkers: job.config.workers ?? "auto"
|
|
12302
12546
|
});
|
|
12303
12547
|
const entryFile = job.config.entryFile || "index.html";
|
|
12304
|
-
let htmlPath =
|
|
12548
|
+
let htmlPath = join26(projectDir, entryFile);
|
|
12305
12549
|
if (!existsSync16(htmlPath)) {
|
|
12306
12550
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
12307
12551
|
}
|
|
12308
12552
|
assertNotAborted();
|
|
12309
12553
|
const rawEntry = readFileSync8(htmlPath, "utf-8");
|
|
12310
12554
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
12311
|
-
const wrapperPath =
|
|
12312
|
-
const projectIndexPath =
|
|
12555
|
+
const wrapperPath = join26(workDir, "standalone-entry.html");
|
|
12556
|
+
const projectIndexPath = join26(projectDir, "index.html");
|
|
12313
12557
|
if (!existsSync16(projectIndexPath)) {
|
|
12314
12558
|
throw new Error(
|
|
12315
12559
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -12472,7 +12716,7 @@ async function executeRenderPipeline(input) {
|
|
|
12472
12716
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
12473
12717
|
code: "extract_video"
|
|
12474
12718
|
});
|
|
12475
|
-
const compiledDir =
|
|
12719
|
+
const compiledDir = join26(workDir, "compiled");
|
|
12476
12720
|
const extractResult = await observeRenderStage(
|
|
12477
12721
|
observability,
|
|
12478
12722
|
"video_extract",
|
|
@@ -12606,7 +12850,7 @@ async function executeRenderPipeline(input) {
|
|
|
12606
12850
|
try {
|
|
12607
12851
|
fileServer = await createFileServer({
|
|
12608
12852
|
projectDir,
|
|
12609
|
-
compiledDir:
|
|
12853
|
+
compiledDir: join26(workDir, "compiled"),
|
|
12610
12854
|
port: 0,
|
|
12611
12855
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
12612
12856
|
fps: job.config.fps
|
|
@@ -12624,7 +12868,7 @@ async function executeRenderPipeline(input) {
|
|
|
12624
12868
|
if (!activeFileServer) {
|
|
12625
12869
|
throw new Error("File server failed to initialize before frame capture");
|
|
12626
12870
|
}
|
|
12627
|
-
const framesDir =
|
|
12871
|
+
const framesDir = join26(workDir, "captured-frames");
|
|
12628
12872
|
if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
|
|
12629
12873
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
12630
12874
|
code: "start_browsers"
|
|
@@ -12673,6 +12917,7 @@ async function executeRenderPipeline(input) {
|
|
|
12673
12917
|
// Probe-resolved duration: drawElement self-verification derives its
|
|
12674
12918
|
// sample frame indices from this so they land inside the drained range.
|
|
12675
12919
|
compositionDurationSeconds: job.duration,
|
|
12920
|
+
deFrameFallback: true,
|
|
12676
12921
|
requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
|
|
12677
12922
|
// Live route for Chrome memory (spec §5 Phase −1). The aggregate route
|
|
12678
12923
|
// through CapturePerfSummary only exists on success; a mid-capture
|
|
@@ -12728,7 +12973,6 @@ async function executeRenderPipeline(input) {
|
|
|
12728
12973
|
const WOULD_RESOLVE_MULTI_WORKER = 2;
|
|
12729
12974
|
const deInversionArgs = {
|
|
12730
12975
|
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
|
12731
|
-
requestedWorkers: job.config.workers,
|
|
12732
12976
|
useDrawElement: cfg.useDrawElement,
|
|
12733
12977
|
deCompileGate,
|
|
12734
12978
|
forceScreenshot: captureForceScreenshot,
|
|
@@ -12774,7 +13018,7 @@ async function executeRenderPipeline(input) {
|
|
|
12774
13018
|
minFrames: deParallelMinFrames,
|
|
12775
13019
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
12776
13020
|
supersampling: deviceScaleFactor !== 1,
|
|
12777
|
-
probeDeGated: isProbeDeGated(probeSession),
|
|
13021
|
+
probeDeGated: isProbeDeGated(probeSession) || probeNeedsScreenshotFrames(probeSession),
|
|
12778
13022
|
experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || process.env.HF_DE_PARALLEL_STREAM === "true",
|
|
12779
13023
|
routerEnabled: deParallelRouterEnabled,
|
|
12780
13024
|
// Router pins 3 workers for the streaming path; don't pin when a
|
|
@@ -12816,6 +13060,8 @@ async function executeRenderPipeline(input) {
|
|
|
12816
13060
|
deInversionEligible = false;
|
|
12817
13061
|
deParallelRouterEligible = false;
|
|
12818
13062
|
deShortBand = void 0;
|
|
13063
|
+
} else if (probeNeedsScreenshotFrames(probeSession)) {
|
|
13064
|
+
deParallelRouterEligible = false;
|
|
12819
13065
|
}
|
|
12820
13066
|
}
|
|
12821
13067
|
const deProbeGateReason = isProbeDeGated(probeSession) ? probeSession.deGateReason : void 0;
|
|
@@ -12934,7 +13180,7 @@ async function executeRenderPipeline(input) {
|
|
|
12934
13180
|
} else if (deInversionEligible && workerCount > 1) {
|
|
12935
13181
|
deWorkerInversion = "inverted";
|
|
12936
13182
|
log.info(
|
|
12937
|
-
`[Render] Fast capture: single-worker drawElement streaming preferred over ${workerCount}-worker screenshot capture (${totalFrames} frames >= ${deInversionEffectiveMinFrames}; verified path, measured faster at every worker count). Set HF_DE_SINGLE_MIN_FRAMES=0
|
|
13183
|
+
`[Render] Fast capture: single-worker drawElement streaming preferred over ${workerCount}-worker screenshot capture (${totalFrames} frames >= ${deInversionEffectiveMinFrames}; verified path, measured faster at every worker count). Set HF_DE_SINGLE_MIN_FRAMES=0 to override.`
|
|
12938
13184
|
);
|
|
12939
13185
|
workerCount = 1;
|
|
12940
13186
|
}
|
|
@@ -12978,6 +13224,27 @@ async function executeRenderPipeline(input) {
|
|
|
12978
13224
|
deWorkerInversion: deWorkerInversion ?? "none",
|
|
12979
13225
|
deParallelRouter: deParallelRouter ?? "none"
|
|
12980
13226
|
});
|
|
13227
|
+
const calibratedTooSlow = checkRenderEta("capture_calibration", {
|
|
13228
|
+
maxRenderMs: job.config.maxRenderMs,
|
|
13229
|
+
elapsedMs: Date.now() - pipelineStart,
|
|
13230
|
+
framesCompleted: 0,
|
|
13231
|
+
totalFrames,
|
|
13232
|
+
msPerFrame: (captureCalibration?.estimate.p95Ms ?? 0) / workerCount
|
|
13233
|
+
});
|
|
13234
|
+
if (calibratedTooSlow) throw calibratedTooSlow;
|
|
13235
|
+
execution.defer(
|
|
13236
|
+
"stop render ETA guard",
|
|
13237
|
+
startRenderEtaGuard({
|
|
13238
|
+
maxRenderMs: job.config.maxRenderMs,
|
|
13239
|
+
job,
|
|
13240
|
+
totalFrames,
|
|
13241
|
+
pipelineStartMs: pipelineStart,
|
|
13242
|
+
onTooSlow: (error) => {
|
|
13243
|
+
tooSlowError = error;
|
|
13244
|
+
tooSlow.abort(error);
|
|
13245
|
+
}
|
|
13246
|
+
})
|
|
13247
|
+
);
|
|
12981
13248
|
let useStreamingEncode = shouldUseStreamingEncode(
|
|
12982
13249
|
cfg,
|
|
12983
13250
|
outputFormat,
|
|
@@ -12986,7 +13253,8 @@ async function executeRenderPipeline(input) {
|
|
|
12986
13253
|
deParallelStreamForced
|
|
12987
13254
|
);
|
|
12988
13255
|
const deParallelStreamVerified = (deParallelStreamForced || process.env.HF_DE_PARALLEL_STREAM === "true") && useStreamingEncode && workerCount > 1;
|
|
12989
|
-
|
|
13256
|
+
const routeDropsScreenshotFrames = cfg.useDrawElement && probeNeedsScreenshotFrames(probeSession) && (!useStreamingEncode || workerCount !== 1);
|
|
13257
|
+
if (routeDropsScreenshotFrames || shouldClampDefaultDrawElement({
|
|
12990
13258
|
useDrawElement: cfg.useDrawElement,
|
|
12991
13259
|
fastCaptureExplicitOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true",
|
|
12992
13260
|
useStreamingEncode,
|
|
@@ -13071,7 +13339,7 @@ async function executeRenderPipeline(input) {
|
|
|
13071
13339
|
hls: ".mp4"
|
|
13072
13340
|
};
|
|
13073
13341
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
13074
|
-
const videoOnlyPath =
|
|
13342
|
+
const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
|
|
13075
13343
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
13076
13344
|
if (usePageSideCompositingForTransitions) {
|
|
13077
13345
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -13317,7 +13585,7 @@ async function executeRenderPipeline(input) {
|
|
|
13317
13585
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
13318
13586
|
captureQuality: captureOptions.quality
|
|
13319
13587
|
});
|
|
13320
|
-
const segmentDir = segmentDirFor(
|
|
13588
|
+
const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
|
|
13321
13589
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
13322
13590
|
if (job.config.resumeSegments === true) {
|
|
13323
13591
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -13332,10 +13600,10 @@ async function executeRenderPipeline(input) {
|
|
|
13332
13600
|
});
|
|
13333
13601
|
}
|
|
13334
13602
|
if (completedSegments.size === 0) {
|
|
13335
|
-
|
|
13603
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13336
13604
|
}
|
|
13337
13605
|
} else {
|
|
13338
|
-
|
|
13606
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13339
13607
|
}
|
|
13340
13608
|
const segmentManifest = readSegmentManifest(segmentDir) ?? {
|
|
13341
13609
|
version: 1,
|
|
@@ -13416,7 +13684,7 @@ async function executeRenderPipeline(input) {
|
|
|
13416
13684
|
);
|
|
13417
13685
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
13418
13686
|
if (job.config.keepSegments !== true) {
|
|
13419
|
-
|
|
13687
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13420
13688
|
}
|
|
13421
13689
|
} else {
|
|
13422
13690
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
@@ -13685,7 +13953,7 @@ async function executeRenderPipeline(input) {
|
|
|
13685
13953
|
"capture_disk",
|
|
13686
13954
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
13687
13955
|
);
|
|
13688
|
-
|
|
13956
|
+
rmSync11(framesDir, { recursive: true, force: true });
|
|
13689
13957
|
mkdirSync9(framesDir, { recursive: true });
|
|
13690
13958
|
resetCaptureAttemptProgress(job);
|
|
13691
13959
|
dedupPerfs.length = 0;
|
|
@@ -13872,7 +14140,7 @@ async function executeRenderPipeline(input) {
|
|
|
13872
14140
|
}
|
|
13873
14141
|
if (job.config.debug) {
|
|
13874
14142
|
if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
|
|
13875
|
-
const debugOutput =
|
|
14143
|
+
const debugOutput = join26(workDir, `output${videoExt}`);
|
|
13876
14144
|
copyFileSync4(stagedOutputPath, debugOutput);
|
|
13877
14145
|
}
|
|
13878
14146
|
}
|
|
@@ -13883,8 +14151,9 @@ async function executeRenderPipeline(input) {
|
|
|
13883
14151
|
job.outputPath = outputPath;
|
|
13884
14152
|
updateJobStatus(job, "complete", "Render complete", 100, onProgress);
|
|
13885
14153
|
await eventPublisher.flush();
|
|
13886
|
-
} catch (
|
|
13887
|
-
|
|
14154
|
+
} catch (caught) {
|
|
14155
|
+
const error = tooSlowError ?? caught;
|
|
14156
|
+
if (!tooSlowError && (error instanceof RenderCancelledError || executionSignal?.aborted)) {
|
|
13888
14157
|
job.error = error instanceof Error ? error.message : "render_cancelled";
|
|
13889
14158
|
updateJobStatus(job, "cancelled", "Render cancelled", job.progress, onProgress);
|
|
13890
14159
|
await eventPublisher.flush();
|
|
@@ -14001,10 +14270,10 @@ import {
|
|
|
14001
14270
|
existsSync as existsSync21,
|
|
14002
14271
|
linkSync,
|
|
14003
14272
|
mkdirSync as mkdirSync13,
|
|
14004
|
-
rmSync as
|
|
14273
|
+
rmSync as rmSync13,
|
|
14005
14274
|
writeFileSync as writeFileSync8
|
|
14006
14275
|
} from "node:fs";
|
|
14007
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
14276
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join34, relative as relative5, sep as sep2 } from "node:path";
|
|
14008
14277
|
import {
|
|
14009
14278
|
fpsToNumber as fpsToNumber10,
|
|
14010
14279
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -14019,7 +14288,7 @@ import {
|
|
|
14019
14288
|
} from "@hyperframes/engine";
|
|
14020
14289
|
|
|
14021
14290
|
// src/services/distributed/planFrameCost.ts
|
|
14022
|
-
import { join as
|
|
14291
|
+
import { join as join29 } from "node:path";
|
|
14023
14292
|
import {
|
|
14024
14293
|
closeCaptureSession as closeCaptureSession9,
|
|
14025
14294
|
createCaptureSession as createCaptureSession9,
|
|
@@ -14029,7 +14298,7 @@ import {
|
|
|
14029
14298
|
|
|
14030
14299
|
// src/services/distributed/chunkFrames.ts
|
|
14031
14300
|
import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
|
|
14032
|
-
import { extname as extname4, join as
|
|
14301
|
+
import { extname as extname4, join as join28 } from "node:path";
|
|
14033
14302
|
import {
|
|
14034
14303
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
14035
14304
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -14039,7 +14308,7 @@ import {
|
|
|
14039
14308
|
|
|
14040
14309
|
// src/services/distributed/shared.ts
|
|
14041
14310
|
import { execFile as execFileCallback } from "node:child_process";
|
|
14042
|
-
import { dirname as dirname11, join as
|
|
14311
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
14043
14312
|
import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
|
|
14044
14313
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
14045
14314
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -14055,7 +14324,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
14055
14324
|
}
|
|
14056
14325
|
function resolvePlanAudioPath(planDir) {
|
|
14057
14326
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
14058
|
-
const candidate =
|
|
14327
|
+
const candidate = join27(planDir, name);
|
|
14059
14328
|
if (existsSync17(candidate)) return candidate;
|
|
14060
14329
|
}
|
|
14061
14330
|
return null;
|
|
@@ -14315,7 +14584,7 @@ function readProducerVersion() {
|
|
|
14315
14584
|
const startDir = dirname11(fileURLToPath5(import.meta.url));
|
|
14316
14585
|
let current = startDir;
|
|
14317
14586
|
for (let i = 0; i < 10; i++) {
|
|
14318
|
-
const candidate =
|
|
14587
|
+
const candidate = join27(current, "package.json");
|
|
14319
14588
|
if (existsSync17(candidate)) {
|
|
14320
14589
|
try {
|
|
14321
14590
|
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
@@ -14369,7 +14638,7 @@ function frameNumberFromFileName(name) {
|
|
|
14369
14638
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
14370
14639
|
const result = [];
|
|
14371
14640
|
for (const v of videos) {
|
|
14372
|
-
const outputDir =
|
|
14641
|
+
const outputDir = join28(planDir, "video-frames", v.videoId);
|
|
14373
14642
|
if (!existsSync18(outputDir)) {
|
|
14374
14643
|
throw new Error(
|
|
14375
14644
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -14388,7 +14657,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
14388
14657
|
if (!frameName) continue;
|
|
14389
14658
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
14390
14659
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
14391
|
-
framePaths.set(frameIndex,
|
|
14660
|
+
framePaths.set(frameIndex, join28(outputDir, frameName));
|
|
14392
14661
|
}
|
|
14393
14662
|
result.push({
|
|
14394
14663
|
videoId: v.videoId,
|
|
@@ -14478,14 +14747,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14478
14747
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
14479
14748
|
return Promise.all(
|
|
14480
14749
|
deferred.map(async (video) => {
|
|
14481
|
-
const outputDir =
|
|
14750
|
+
const outputDir = join28(input.outputRoot, video.videoId);
|
|
14482
14751
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
14483
14752
|
if (indexes.length === 0) {
|
|
14484
14753
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
14485
14754
|
}
|
|
14486
14755
|
const range = video.deferredRange;
|
|
14487
14756
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
14488
|
-
const sourcePath = remoteUrl ??
|
|
14757
|
+
const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
|
|
14489
14758
|
if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
|
|
14490
14759
|
throw new RenderChunkValidationError(
|
|
14491
14760
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -14534,12 +14803,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14534
14803
|
planVideos,
|
|
14535
14804
|
frames: sampled,
|
|
14536
14805
|
fps: fpsRatio,
|
|
14537
|
-
outputRoot:
|
|
14806
|
+
outputRoot: join29(workDir, "frame-cost-video-frames"),
|
|
14538
14807
|
cfg: input.cfg
|
|
14539
14808
|
})
|
|
14540
14809
|
];
|
|
14541
14810
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
14542
|
-
const compiledDir =
|
|
14811
|
+
const compiledDir = join29(planDir, "compiled");
|
|
14543
14812
|
const fileServer = await createFileServer({
|
|
14544
14813
|
projectDir: compiledDir,
|
|
14545
14814
|
compiledDir,
|
|
@@ -14551,7 +14820,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14551
14820
|
try {
|
|
14552
14821
|
session = await createCaptureSession9(
|
|
14553
14822
|
fileServer.url,
|
|
14554
|
-
|
|
14823
|
+
join29(workDir, "frame-cost"),
|
|
14555
14824
|
buildChunkCaptureOptions({
|
|
14556
14825
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
14557
14826
|
encoder: input,
|
|
@@ -14609,6 +14878,18 @@ function planFrameWeights(input) {
|
|
|
14609
14878
|
}
|
|
14610
14879
|
return weights;
|
|
14611
14880
|
}
|
|
14881
|
+
function withChunkCaptureCost(slices, weights) {
|
|
14882
|
+
const lightest = weights.reduce((least, weight) => Math.min(least, weight), Infinity);
|
|
14883
|
+
return slices.map((slice) => {
|
|
14884
|
+
let sum2 = 0;
|
|
14885
|
+
for (let frame = slice.startFrame; frame < slice.endFrame; frame++) sum2 += weights[frame];
|
|
14886
|
+
const cost = Math.round(sum2 / (slice.endFrame - slice.startFrame) / lightest * 100) / 100;
|
|
14887
|
+
return cost > 1 ? { ...slice, captureCost: cost } : slice;
|
|
14888
|
+
});
|
|
14889
|
+
}
|
|
14890
|
+
function effectiveChunkFrames(frames, captureCost = 1) {
|
|
14891
|
+
return Math.round(frames * captureCost);
|
|
14892
|
+
}
|
|
14612
14893
|
function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
14613
14894
|
if (isUniformFrameCost(weights)) return null;
|
|
14614
14895
|
const totalFrames = weights.length;
|
|
@@ -14637,7 +14918,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
14637
14918
|
|
|
14638
14919
|
// src/services/render/stages/freezePlan.ts
|
|
14639
14920
|
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14640
|
-
import { join as
|
|
14921
|
+
import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
|
|
14641
14922
|
|
|
14642
14923
|
// src/services/distributed/planProtocol.ts
|
|
14643
14924
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -14736,7 +15017,7 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
14736
15017
|
|
|
14737
15018
|
// src/services/render/stages/planHash.ts
|
|
14738
15019
|
import { createHash as createHash6 } from "node:crypto";
|
|
14739
|
-
import { closeSync as
|
|
15020
|
+
import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
|
|
14740
15021
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
14741
15022
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
14742
15023
|
function computePlanHash(input) {
|
|
@@ -14802,7 +15083,7 @@ function sha256File(path) {
|
|
|
14802
15083
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
14803
15084
|
} while (bytesRead > 0);
|
|
14804
15085
|
} finally {
|
|
14805
|
-
|
|
15086
|
+
closeSync4(fd);
|
|
14806
15087
|
}
|
|
14807
15088
|
return hash.digest("hex");
|
|
14808
15089
|
}
|
|
@@ -14855,7 +15136,7 @@ function listPlanFiles(planDir) {
|
|
|
14855
15136
|
function walk(dir) {
|
|
14856
15137
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14857
15138
|
for (const entry of entries) {
|
|
14858
|
-
const full =
|
|
15139
|
+
const full = join30(dir, entry.name);
|
|
14859
15140
|
if (entry.isDirectory()) {
|
|
14860
15141
|
walk(full);
|
|
14861
15142
|
} else if (entry.isFile()) {
|
|
@@ -14896,15 +15177,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
14896
15177
|
function writePlanRemoteFiles(planDir, files) {
|
|
14897
15178
|
if (files.length === 0) return;
|
|
14898
15179
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
14899
|
-
writeFileSync6(
|
|
15180
|
+
writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
14900
15181
|
}
|
|
14901
15182
|
function readPlanRemoteFiles(planDir) {
|
|
14902
|
-
const path =
|
|
15183
|
+
const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
14903
15184
|
return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
14904
15185
|
}
|
|
14905
15186
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
14906
|
-
const planJsonPath =
|
|
14907
|
-
const encoderJsonPath =
|
|
15187
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
15188
|
+
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
14908
15189
|
if (!existsSync19(planJsonPath)) {
|
|
14909
15190
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
14910
15191
|
}
|
|
@@ -14941,18 +15222,18 @@ async function freezePlan(input) {
|
|
|
14941
15222
|
if (!existsSync19(planDir)) {
|
|
14942
15223
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
14943
15224
|
}
|
|
14944
|
-
const metaDir =
|
|
15225
|
+
const metaDir = join30(planDir, "meta");
|
|
14945
15226
|
if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
|
|
14946
15227
|
writeFileSync6(
|
|
14947
|
-
|
|
15228
|
+
join30(metaDir, "composition.json"),
|
|
14948
15229
|
`${JSON.stringify(composition, null, 2)}
|
|
14949
15230
|
`,
|
|
14950
15231
|
"utf-8"
|
|
14951
15232
|
);
|
|
14952
15233
|
const encoderForCanonical = stripUndefined(encoder);
|
|
14953
15234
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
14954
|
-
writeFileSync6(
|
|
14955
|
-
writeFileSync6(
|
|
15235
|
+
writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
15236
|
+
writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
14956
15237
|
`, "utf-8");
|
|
14957
15238
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
14958
15239
|
const planHash = computePlanHash({
|
|
@@ -14976,7 +15257,7 @@ async function freezePlan(input) {
|
|
|
14976
15257
|
duration: durationSeconds,
|
|
14977
15258
|
hasAudio
|
|
14978
15259
|
};
|
|
14979
|
-
const planJsonPath =
|
|
15260
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
14980
15261
|
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
14981
15262
|
`, "utf-8");
|
|
14982
15263
|
return { planJsonPath, planHash };
|
|
@@ -15018,16 +15299,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
15018
15299
|
}
|
|
15019
15300
|
|
|
15020
15301
|
// src/services/distributed/chunkAudio.ts
|
|
15021
|
-
import { closeSync as
|
|
15302
|
+
import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
15022
15303
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
15023
|
-
import { join as
|
|
15304
|
+
import { join as join32 } from "node:path";
|
|
15024
15305
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
15025
15306
|
|
|
15026
15307
|
// src/services/distributed/mp4Stitch.ts
|
|
15027
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as
|
|
15308
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync12 } from "node:fs";
|
|
15028
15309
|
import { open } from "node:fs/promises";
|
|
15029
15310
|
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
15030
|
-
import { dirname as dirname12, join as
|
|
15311
|
+
import { dirname as dirname12, join as join31 } from "node:path";
|
|
15031
15312
|
import { finished, pipeline } from "node:stream/promises";
|
|
15032
15313
|
function boxAt(src, off, at, limit) {
|
|
15033
15314
|
let size = src.readUInt32BE(off);
|
|
@@ -15393,16 +15674,16 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15393
15674
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15394
15675
|
mkdirSync11(dirname12(outputPath), { recursive: true });
|
|
15395
15676
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15396
|
-
const out = createWriteStream(
|
|
15677
|
+
const out = createWriteStream(join31(workDir, "out.mp4"));
|
|
15397
15678
|
try {
|
|
15398
15679
|
out.write(header);
|
|
15399
15680
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15400
15681
|
out.end();
|
|
15401
15682
|
await finished(out);
|
|
15402
|
-
renameWithRetry5(
|
|
15683
|
+
renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
|
|
15403
15684
|
} finally {
|
|
15404
15685
|
out.destroy();
|
|
15405
|
-
|
|
15686
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
15406
15687
|
}
|
|
15407
15688
|
}
|
|
15408
15689
|
|
|
@@ -15493,7 +15774,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
15493
15774
|
});
|
|
15494
15775
|
const bytes = readFileSync11(input.audioPath);
|
|
15495
15776
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
15496
|
-
mkdirSync12(
|
|
15777
|
+
mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
|
|
15497
15778
|
input.chunks.forEach((_, index) => {
|
|
15498
15779
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
15499
15780
|
if (!cut) return;
|
|
@@ -15501,17 +15782,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
15501
15782
|
const start = offsets[cut.firstPacket];
|
|
15502
15783
|
layout.partStarts[index] = start;
|
|
15503
15784
|
const end = offsets[last] + track.sizes[last];
|
|
15504
|
-
writeFileSync7(
|
|
15785
|
+
writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
15505
15786
|
});
|
|
15506
15787
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
15507
|
-
writeFileSync7(
|
|
15508
|
-
writeFileSync7(
|
|
15788
|
+
writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
15789
|
+
writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
15509
15790
|
}
|
|
15510
15791
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
15511
|
-
const layoutPath =
|
|
15792
|
+
const layoutPath = join32(planDir, SLICE_LAYOUT);
|
|
15512
15793
|
if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
15513
15794
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
15514
|
-
const frame = readFileSync11(
|
|
15795
|
+
const frame = readFileSync11(join32(planDir, SLICE_FRAME));
|
|
15515
15796
|
const fd = openSync5(scratchPath, "w");
|
|
15516
15797
|
try {
|
|
15517
15798
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -15524,12 +15805,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
15524
15805
|
);
|
|
15525
15806
|
const start = layout.partStarts[chunkIndex];
|
|
15526
15807
|
if (start !== void 0) {
|
|
15527
|
-
const part = readFileSync11(
|
|
15808
|
+
const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
15528
15809
|
writeSync(fd, part, 0, part.length, start);
|
|
15529
15810
|
}
|
|
15530
15811
|
ftruncateSync(fd, layout.sizeBytes);
|
|
15531
15812
|
} finally {
|
|
15532
|
-
|
|
15813
|
+
closeSync5(fd);
|
|
15533
15814
|
}
|
|
15534
15815
|
return scratchPath;
|
|
15535
15816
|
}
|
|
@@ -15580,7 +15861,7 @@ async function muxChunkWithAudio(input) {
|
|
|
15580
15861
|
// src/services/distributed/planSize.ts
|
|
15581
15862
|
import { createHash as createHash7 } from "node:crypto";
|
|
15582
15863
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
15583
|
-
import { extname as extname5, join as
|
|
15864
|
+
import { extname as extname5, join as join33, relative as relative4 } from "node:path";
|
|
15584
15865
|
var MAX_TOP_COMPONENTS = 10;
|
|
15585
15866
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
15586
15867
|
".aac",
|
|
@@ -15692,7 +15973,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
15692
15973
|
return;
|
|
15693
15974
|
}
|
|
15694
15975
|
for (const entry of entries) {
|
|
15695
|
-
const full =
|
|
15976
|
+
const full = join33(dir, entry.name);
|
|
15696
15977
|
if (entry.isDirectory()) {
|
|
15697
15978
|
walkRegularFiles(full, visit);
|
|
15698
15979
|
} else if (entry.isFile()) {
|
|
@@ -15904,13 +16185,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
15904
16185
|
}
|
|
15905
16186
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
15906
16187
|
"plan.json",
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
16188
|
+
join34("meta", "composition.json"),
|
|
16189
|
+
join34("meta", "encoder.json"),
|
|
16190
|
+
join34("meta", "chunks.json")
|
|
15910
16191
|
];
|
|
15911
16192
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
15912
16193
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
15913
|
-
|
|
16194
|
+
rmSync13(join34(planDir, relativePath), { force: true });
|
|
15914
16195
|
}
|
|
15915
16196
|
}
|
|
15916
16197
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -15929,7 +16210,10 @@ function buildPlanChunkSlices(frameWeights, config, chunkCount, effectiveChunkSi
|
|
|
15929
16210
|
MIN_CHUNK_SIZE,
|
|
15930
16211
|
Math.min(config.targetChunkFrames ?? MAX_CHUNK_SIZE, MAX_CHUNK_SIZE, maxFramesPerChunk)
|
|
15931
16212
|
) : null;
|
|
15932
|
-
return
|
|
16213
|
+
return withChunkCaptureCost(
|
|
16214
|
+
weighted ?? buildChunkSlices(frameWeights.length, chunkCount, effectiveChunkSize),
|
|
16215
|
+
frameWeights
|
|
16216
|
+
);
|
|
15933
16217
|
}
|
|
15934
16218
|
async function readFontSnapshotSha() {
|
|
15935
16219
|
const module = await import("@hyperframes/core/fonts/embedded-data");
|
|
@@ -16052,19 +16336,19 @@ function createVideoSourceShipper(input) {
|
|
|
16052
16336
|
const known = shipped.get(srcPath);
|
|
16053
16337
|
if (known) return known;
|
|
16054
16338
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
16055
|
-
const current = stagedRel === null ? srcPath :
|
|
16339
|
+
const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
|
|
16056
16340
|
let result = null;
|
|
16057
16341
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
16058
16342
|
const rel = pathInside(root, srcPath);
|
|
16059
|
-
if (rel !== null && (existsSync21(
|
|
16343
|
+
if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
16060
16344
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
16061
16345
|
break;
|
|
16062
16346
|
}
|
|
16063
16347
|
}
|
|
16064
16348
|
if (result === null) {
|
|
16065
16349
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
16066
|
-
const destination =
|
|
16067
|
-
mkdirSync13(
|
|
16350
|
+
const destination = join34(input.planDir, ...result.split("/"));
|
|
16351
|
+
mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
|
|
16068
16352
|
try {
|
|
16069
16353
|
linkSync(current, destination);
|
|
16070
16354
|
} catch {
|
|
@@ -16113,14 +16397,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16113
16397
|
variables: config.variables
|
|
16114
16398
|
});
|
|
16115
16399
|
const entryFile = config.entryFile ?? "index.html";
|
|
16116
|
-
const htmlPath =
|
|
16400
|
+
const htmlPath = join34(projectDir, entryFile);
|
|
16117
16401
|
if (!existsSync21(htmlPath)) {
|
|
16118
16402
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16119
16403
|
}
|
|
16120
|
-
const workDir =
|
|
16121
|
-
|
|
16404
|
+
const workDir = join34(planDir, ".plan-work");
|
|
16405
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16122
16406
|
mkdirSync13(workDir, { recursive: true });
|
|
16123
|
-
const compiledDir =
|
|
16407
|
+
const compiledDir = join34(workDir, "compiled");
|
|
16124
16408
|
mkdirSync13(compiledDir, { recursive: true });
|
|
16125
16409
|
let stageStarted = Date.now();
|
|
16126
16410
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -16134,10 +16418,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16134
16418
|
}
|
|
16135
16419
|
});
|
|
16136
16420
|
logPlanStage(log, "copy", stageStarted);
|
|
16137
|
-
const finalCompiledDir =
|
|
16421
|
+
const finalCompiledDir = join34(planDir, "compiled");
|
|
16138
16422
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
16139
16423
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
16140
|
-
const remoteLocalPath = (rel) =>
|
|
16424
|
+
const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
|
|
16141
16425
|
for (const [, file] of remoteFiles) {
|
|
16142
16426
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
16143
16427
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -16323,17 +16607,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16323
16607
|
let stageStarted = Date.now();
|
|
16324
16608
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
16325
16609
|
logPlanStage(log, "extract", stageStarted);
|
|
16326
|
-
const stagedVideoFrames =
|
|
16327
|
-
const videoFramesDst =
|
|
16328
|
-
if (existsSync21(videoFramesDst))
|
|
16329
|
-
const videoSourcesDir =
|
|
16330
|
-
|
|
16610
|
+
const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
|
|
16611
|
+
const videoFramesDst = join34(planDir, "video-frames");
|
|
16612
|
+
if (existsSync21(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
|
|
16613
|
+
const videoSourcesDir = join34(planDir, "video-sources");
|
|
16614
|
+
rmSync13(videoSourcesDir, { recursive: true, force: true });
|
|
16331
16615
|
if (existsSync21(stagedVideoFrames)) {
|
|
16332
16616
|
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
16333
16617
|
} else {
|
|
16334
16618
|
mkdirSync13(videoFramesDst, { recursive: true });
|
|
16335
16619
|
}
|
|
16336
|
-
if (existsSync21(finalCompiledDir))
|
|
16620
|
+
if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
|
|
16337
16621
|
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
16338
16622
|
const shipVideoSource = createVideoSourceShipper({
|
|
16339
16623
|
planDir,
|
|
@@ -16363,7 +16647,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16363
16647
|
};
|
|
16364
16648
|
})
|
|
16365
16649
|
});
|
|
16366
|
-
mkdirSync13(
|
|
16650
|
+
mkdirSync13(join34(planDir, "meta"), { recursive: true });
|
|
16367
16651
|
writePlanRemoteFiles(
|
|
16368
16652
|
planDir,
|
|
16369
16653
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -16373,7 +16657,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16373
16657
|
}))
|
|
16374
16658
|
);
|
|
16375
16659
|
writeFileSync8(
|
|
16376
|
-
|
|
16660
|
+
join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
16377
16661
|
JSON.stringify(planVideosJson, null, 2),
|
|
16378
16662
|
"utf-8"
|
|
16379
16663
|
);
|
|
@@ -16452,7 +16736,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16452
16736
|
if (audioResult?.audioError) {
|
|
16453
16737
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
16454
16738
|
}
|
|
16455
|
-
const planAudioPath =
|
|
16739
|
+
const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16456
16740
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16457
16741
|
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
16458
16742
|
}
|
|
@@ -16501,7 +16785,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16501
16785
|
format: config.format
|
|
16502
16786
|
};
|
|
16503
16787
|
try {
|
|
16504
|
-
|
|
16788
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16505
16789
|
} catch (err) {
|
|
16506
16790
|
log.warn("[plan] failed to remove temp work dir", {
|
|
16507
16791
|
workDir,
|
|
@@ -16876,6 +17160,7 @@ function assertRequestOptionScalars(options) {
|
|
|
16876
17160
|
assertOptionalInteger(options, "gifLoop");
|
|
16877
17161
|
assertOptionalInteger(options, "hlsSegmentSeconds", 1);
|
|
16878
17162
|
assertOptionalInteger(options, "workers", 1);
|
|
17163
|
+
assertOptionalInteger(options, "maxRenderMs");
|
|
16879
17164
|
assertOptionalInteger(options, "crf");
|
|
16880
17165
|
for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
|
|
16881
17166
|
assertOptionalBoolean(options, field);
|
|
@@ -17072,13 +17357,13 @@ import {
|
|
|
17072
17357
|
import {
|
|
17073
17358
|
existsSync as existsSync24,
|
|
17074
17359
|
mkdirSync as mkdirSync14,
|
|
17075
|
-
statSync as
|
|
17360
|
+
statSync as statSync9,
|
|
17076
17361
|
mkdtempSync as mkdtempSync5,
|
|
17077
17362
|
writeFileSync as writeFileSync9,
|
|
17078
|
-
rmSync as
|
|
17363
|
+
rmSync as rmSync14,
|
|
17079
17364
|
createReadStream as createReadStream5
|
|
17080
17365
|
} from "node:fs";
|
|
17081
|
-
import { resolve as resolve10, dirname as dirname14, join as
|
|
17366
|
+
import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
|
|
17082
17367
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17083
17368
|
import { parseArgs } from "node:util";
|
|
17084
17369
|
import crypto from "node:crypto";
|
|
@@ -17091,13 +17376,13 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
17091
17376
|
import {
|
|
17092
17377
|
existsSync as existsSync22,
|
|
17093
17378
|
readFileSync as readFileSync12,
|
|
17094
|
-
statSync as
|
|
17379
|
+
statSync as statSync8,
|
|
17095
17380
|
openSync as openSync6,
|
|
17096
|
-
fstatSync as
|
|
17097
|
-
closeSync as
|
|
17381
|
+
fstatSync as fstatSync3,
|
|
17382
|
+
closeSync as closeSync6,
|
|
17098
17383
|
constants as constants3
|
|
17099
17384
|
} from "node:fs";
|
|
17100
|
-
import { resolve as resolve9, join as
|
|
17385
|
+
import { resolve as resolve9, join as join35, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17101
17386
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
17102
17387
|
function isStringRecord(value) {
|
|
17103
17388
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -17131,19 +17416,19 @@ function readEntryHtml(filePath) {
|
|
|
17131
17416
|
} catch (error) {
|
|
17132
17417
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
17133
17418
|
return null;
|
|
17134
|
-
if (!
|
|
17419
|
+
if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
17135
17420
|
throw error;
|
|
17136
17421
|
}
|
|
17137
17422
|
try {
|
|
17138
|
-
if (!
|
|
17423
|
+
if (!fstatSync3(fd).isFile()) return null;
|
|
17139
17424
|
return readFileSync12(fd, "utf-8");
|
|
17140
17425
|
} finally {
|
|
17141
|
-
|
|
17426
|
+
closeSync6(fd);
|
|
17142
17427
|
}
|
|
17143
17428
|
}
|
|
17144
17429
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
17145
17430
|
const absProjectDir = resolve9(projectDir);
|
|
17146
|
-
if (!existsSync22(absProjectDir) || !
|
|
17431
|
+
if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
17147
17432
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17148
17433
|
}
|
|
17149
17434
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -17165,7 +17450,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
17165
17450
|
}
|
|
17166
17451
|
}
|
|
17167
17452
|
return {
|
|
17168
|
-
error: `No HTML entry file found in project directory: ${
|
|
17453
|
+
error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
|
|
17169
17454
|
};
|
|
17170
17455
|
}
|
|
17171
17456
|
function prepareHyperframeLintBody(body) {
|
|
@@ -17211,7 +17496,7 @@ async function runHyperframeLint(prepared) {
|
|
|
17211
17496
|
// src/services/healthWorker.ts
|
|
17212
17497
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
17213
17498
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17214
|
-
import { dirname as dirname13, join as
|
|
17499
|
+
import { dirname as dirname13, join as join36 } from "node:path";
|
|
17215
17500
|
import { existsSync as existsSync23 } from "node:fs";
|
|
17216
17501
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
17217
17502
|
async function startHealthWorker(options = {}) {
|
|
@@ -17287,7 +17572,7 @@ function defaultLogger2() {
|
|
|
17287
17572
|
}
|
|
17288
17573
|
function resolveWorkerEntry2() {
|
|
17289
17574
|
const here = dirname13(fileURLToPath6(import.meta.url));
|
|
17290
|
-
const candidates = [
|
|
17575
|
+
const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
|
|
17291
17576
|
for (const candidate of candidates) {
|
|
17292
17577
|
if (existsSync23(candidate)) return candidate;
|
|
17293
17578
|
}
|
|
@@ -17308,7 +17593,8 @@ var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
17308
17593
|
"INVALID_VIDEO_METADATA",
|
|
17309
17594
|
"VIDEO_SOURCE_UNRENDERABLE",
|
|
17310
17595
|
"VIDEO_EXTRACTION_FAILED",
|
|
17311
|
-
"ENCODER_INTERRUPTED"
|
|
17596
|
+
"ENCODER_INTERRUPTED",
|
|
17597
|
+
"RENDER_TOO_SLOW"
|
|
17312
17598
|
]);
|
|
17313
17599
|
function extractSafeRenderErrorCode(error) {
|
|
17314
17600
|
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
@@ -17367,6 +17653,7 @@ function parseRenderOptions(body) {
|
|
|
17367
17653
|
const fps = parseServerFps(body.fps);
|
|
17368
17654
|
const quality = parseServerQuality(body.quality);
|
|
17369
17655
|
const workers = typeof body.workers === "number" ? body.workers : void 0;
|
|
17656
|
+
const maxRenderMs = typeof body.maxRenderMs === "number" ? body.maxRenderMs : void 0;
|
|
17370
17657
|
const useGpu = body.gpu === true;
|
|
17371
17658
|
const debug = body.debug === true;
|
|
17372
17659
|
const strictness = body.bestEffort === false ? "strict" : "best-effort";
|
|
@@ -17382,6 +17669,7 @@ function parseRenderOptions(body) {
|
|
|
17382
17669
|
fps,
|
|
17383
17670
|
quality,
|
|
17384
17671
|
workers,
|
|
17672
|
+
maxRenderMs,
|
|
17385
17673
|
crf,
|
|
17386
17674
|
videoBitrate,
|
|
17387
17675
|
useGpu,
|
|
@@ -17415,6 +17703,7 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
17415
17703
|
quality: input.quality,
|
|
17416
17704
|
format: input.format ?? "mp4",
|
|
17417
17705
|
workers: input.workers,
|
|
17706
|
+
maxRenderMs: input.maxRenderMs,
|
|
17418
17707
|
crf: input.crf,
|
|
17419
17708
|
videoBitrate: input.videoBitrate,
|
|
17420
17709
|
useGpu: input.useGpu,
|
|
@@ -17441,6 +17730,9 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
17441
17730
|
function validateRenderOverrides(body) {
|
|
17442
17731
|
const encoder = parseEncoderOptions(body, parseServerFormat(body.format) ?? "mp4");
|
|
17443
17732
|
if (!encoder.ok) return encoder.error;
|
|
17733
|
+
if (body.maxRenderMs !== void 0 && (typeof body.maxRenderMs !== "number" || !Number.isInteger(body.maxRenderMs) || body.maxRenderMs < 0)) {
|
|
17734
|
+
return "maxRenderMs must be a non-negative integer number of milliseconds";
|
|
17735
|
+
}
|
|
17444
17736
|
if (body.variables !== void 0 && !isPlainObject2(body.variables)) {
|
|
17445
17737
|
return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
|
|
17446
17738
|
}
|
|
@@ -17473,7 +17765,7 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
17473
17765
|
const candidate = nonEmptyString(projectDir);
|
|
17474
17766
|
if (!candidate) return null;
|
|
17475
17767
|
const absProjectDir = resolve10(candidate);
|
|
17476
|
-
if (!existsSync24(absProjectDir) || !
|
|
17768
|
+
if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
17477
17769
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17478
17770
|
}
|
|
17479
17771
|
const entry = options.entryFile || "index.html";
|
|
@@ -17498,8 +17790,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17498
17790
|
}
|
|
17499
17791
|
function materializeInlineProject(html, options) {
|
|
17500
17792
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
17501
|
-
const tempProjectDir = mkdtempSync5(
|
|
17502
|
-
writeFileSync9(
|
|
17793
|
+
const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
|
|
17794
|
+
writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
|
|
17503
17795
|
return {
|
|
17504
17796
|
prepared: {
|
|
17505
17797
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -17560,7 +17852,7 @@ function createArtifactStore(ttlMs) {
|
|
|
17560
17852
|
function cleanupTempDir(dir, log) {
|
|
17561
17853
|
if (!dir) return;
|
|
17562
17854
|
try {
|
|
17563
|
-
|
|
17855
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
17564
17856
|
} catch (error) {
|
|
17565
17857
|
log.warn("Failed to cleanup temp project dir", {
|
|
17566
17858
|
cleanupProjectDir: dir,
|
|
@@ -17569,7 +17861,7 @@ function cleanupTempDir(dir, log) {
|
|
|
17569
17861
|
}
|
|
17570
17862
|
}
|
|
17571
17863
|
function outputFileSize(path) {
|
|
17572
|
-
return existsSync24(path) ?
|
|
17864
|
+
return existsSync24(path) ? statSync9(path).size : 0;
|
|
17573
17865
|
}
|
|
17574
17866
|
function createBlockingProgressReporter(log, requestId) {
|
|
17575
17867
|
let lastLoggedPct = -10;
|
|
@@ -17869,7 +18161,7 @@ function createRenderHandlers(options = {}) {
|
|
|
17869
18161
|
store.delete(token);
|
|
17870
18162
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
17871
18163
|
}
|
|
17872
|
-
const stats =
|
|
18164
|
+
const stats = statSync9(artifact.path);
|
|
17873
18165
|
return new Response(createReadStream5(artifact.path), {
|
|
17874
18166
|
headers: {
|
|
17875
18167
|
"content-type": "video/mp4",
|
|
@@ -17968,12 +18260,12 @@ import {
|
|
|
17968
18260
|
mkdtempSync as mkdtempSync7,
|
|
17969
18261
|
readFileSync as readFileSync13,
|
|
17970
18262
|
readdirSync as readdirSync9,
|
|
17971
|
-
rmSync as
|
|
17972
|
-
statSync as
|
|
18263
|
+
rmSync as rmSync16,
|
|
18264
|
+
statSync as statSync11,
|
|
17973
18265
|
writeFileSync as writeFileSync11
|
|
17974
18266
|
} from "node:fs";
|
|
17975
18267
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
17976
|
-
import { dirname as dirname16, isAbsolute as isAbsolute5, join as
|
|
18268
|
+
import { dirname as dirname16, isAbsolute as isAbsolute5, join as join40, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
|
|
17977
18269
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
17978
18270
|
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
17979
18271
|
|
|
@@ -17984,12 +18276,12 @@ import {
|
|
|
17984
18276
|
linkSync as linkSync2,
|
|
17985
18277
|
mkdirSync as mkdirSync15,
|
|
17986
18278
|
mkdtempSync as mkdtempSync6,
|
|
17987
|
-
rmSync as
|
|
17988
|
-
statSync as
|
|
18279
|
+
rmSync as rmSync15,
|
|
18280
|
+
statSync as statSync10,
|
|
17989
18281
|
writeFileSync as writeFileSync10
|
|
17990
18282
|
} from "node:fs";
|
|
17991
18283
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
17992
|
-
import { dirname as dirname15, join as
|
|
18284
|
+
import { dirname as dirname15, join as join39 } from "node:path";
|
|
17993
18285
|
|
|
17994
18286
|
// src/services/distributed/planV2Errors.ts
|
|
17995
18287
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -18004,7 +18296,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
18004
18296
|
};
|
|
18005
18297
|
|
|
18006
18298
|
// src/services/distributed/planV2Layout.ts
|
|
18007
|
-
import { join as
|
|
18299
|
+
import { join as join38 } from "node:path";
|
|
18008
18300
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18009
18301
|
function assertPlanV2Sha256(value, field) {
|
|
18010
18302
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -18014,7 +18306,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
18014
18306
|
}
|
|
18015
18307
|
function planV2BlobPath(root, sha256) {
|
|
18016
18308
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
18017
|
-
return
|
|
18309
|
+
return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
18018
18310
|
}
|
|
18019
18311
|
|
|
18020
18312
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18055,11 +18347,11 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18055
18347
|
this.destinationDir = destinationDir;
|
|
18056
18348
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18057
18349
|
mkdirSync15(dirname15(destinationDir), { recursive: true });
|
|
18058
|
-
this.temporaryDir = mkdtempSync6(
|
|
18350
|
+
this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
|
|
18059
18351
|
}
|
|
18060
18352
|
async putBlob(blob) {
|
|
18061
18353
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
18062
|
-
const sourceSize =
|
|
18354
|
+
const sourceSize = statSync10(blob.sourcePath).size;
|
|
18063
18355
|
if (sourceSize !== blob.sizeBytes) {
|
|
18064
18356
|
throw new PlanV2IntegrityError(
|
|
18065
18357
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -18068,8 +18360,8 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18068
18360
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18069
18361
|
if (existsSync25(destinationPath)) return;
|
|
18070
18362
|
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
18071
|
-
const stagingDir = mkdtempSync6(
|
|
18072
|
-
const temporaryPath =
|
|
18363
|
+
const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18364
|
+
const temporaryPath = join39(stagingDir, "blob");
|
|
18073
18365
|
try {
|
|
18074
18366
|
try {
|
|
18075
18367
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -18079,7 +18371,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18079
18371
|
}
|
|
18080
18372
|
renameWithRetry7(temporaryPath, destinationPath);
|
|
18081
18373
|
} finally {
|
|
18082
|
-
|
|
18374
|
+
rmSync15(stagingDir, { recursive: true, force: true });
|
|
18083
18375
|
}
|
|
18084
18376
|
}
|
|
18085
18377
|
async commitManifest(manifestBytes) {
|
|
@@ -18090,13 +18382,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18090
18382
|
);
|
|
18091
18383
|
}
|
|
18092
18384
|
}
|
|
18093
|
-
writeFileSync10(
|
|
18385
|
+
writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18094
18386
|
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
18095
18387
|
this.#committed = true;
|
|
18096
18388
|
}
|
|
18097
18389
|
async abort() {
|
|
18098
18390
|
if (!this.#committed) {
|
|
18099
|
-
|
|
18391
|
+
rmSync15(this.temporaryDir, { recursive: true, force: true });
|
|
18100
18392
|
}
|
|
18101
18393
|
}
|
|
18102
18394
|
};
|
|
@@ -18135,7 +18427,7 @@ function listFiles(root) {
|
|
|
18135
18427
|
const rootResolved = resolve11(root);
|
|
18136
18428
|
function walk(dir) {
|
|
18137
18429
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18138
|
-
const absolutePath =
|
|
18430
|
+
const absolutePath = join40(dir, entry.name);
|
|
18139
18431
|
if (entry.isDirectory()) {
|
|
18140
18432
|
walk(absolutePath);
|
|
18141
18433
|
} else if (entry.isFile()) {
|
|
@@ -18162,13 +18454,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18162
18454
|
}
|
|
18163
18455
|
}
|
|
18164
18456
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18165
|
-
const videoRoot =
|
|
18457
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18166
18458
|
if (!existsSync26(videoRoot)) return;
|
|
18167
18459
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18168
18460
|
if (!videoEntry.isDirectory()) continue;
|
|
18169
|
-
const videoDir =
|
|
18461
|
+
const videoDir = join40(videoRoot, videoEntry.name);
|
|
18170
18462
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18171
|
-
const sentinelPath =
|
|
18463
|
+
const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18172
18464
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18173
18465
|
}
|
|
18174
18466
|
}
|
|
@@ -18215,7 +18507,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18215
18507
|
if (video.deferredRange) {
|
|
18216
18508
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18217
18509
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18218
|
-
framePaths2.set(index,
|
|
18510
|
+
framePaths2.set(index, join40(outputDir, String(index)));
|
|
18219
18511
|
}
|
|
18220
18512
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18221
18513
|
}
|
|
@@ -18223,7 +18515,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18223
18515
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18224
18516
|
for (const frameName of frameNames) {
|
|
18225
18517
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18226
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18518
|
+
assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
|
|
18227
18519
|
continue;
|
|
18228
18520
|
}
|
|
18229
18521
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18242,7 +18534,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18242
18534
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18243
18535
|
);
|
|
18244
18536
|
}
|
|
18245
|
-
framePaths.set(frameIndex,
|
|
18537
|
+
framePaths.set(frameIndex, join40(outputDir, frameName));
|
|
18246
18538
|
}
|
|
18247
18539
|
return {
|
|
18248
18540
|
...video,
|
|
@@ -18263,7 +18555,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18263
18555
|
}
|
|
18264
18556
|
}
|
|
18265
18557
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18266
|
-
const videosPath =
|
|
18558
|
+
const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18267
18559
|
if (!existsSync26(videosPath)) return;
|
|
18268
18560
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18269
18561
|
for (const video of videos.extracted) {
|
|
@@ -18288,17 +18580,22 @@ function parseChunkSlices(value) {
|
|
|
18288
18580
|
throw new PlanV2IntegrityError(`meta/chunks.json contains duplicate chunk index ${index}`);
|
|
18289
18581
|
}
|
|
18290
18582
|
indexes.add(index);
|
|
18291
|
-
return { index, startFrame, endFrame };
|
|
18583
|
+
if (chunk.captureCost === void 0) return { index, startFrame, endFrame };
|
|
18584
|
+
const captureCost = chunk.captureCost;
|
|
18585
|
+
if (typeof captureCost !== "number" || !Number.isFinite(captureCost) || captureCost <= 1) {
|
|
18586
|
+
throw new PlanV2IntegrityError(`${field}.captureCost must be a number above 1`);
|
|
18587
|
+
}
|
|
18588
|
+
return { index, startFrame, endFrame, captureCost };
|
|
18292
18589
|
});
|
|
18293
18590
|
}
|
|
18294
18591
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18295
|
-
const videoRoot =
|
|
18592
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18296
18593
|
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18297
|
-
const videosPath =
|
|
18594
|
+
const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18298
18595
|
if (!existsSync26(videosPath)) {
|
|
18299
18596
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18300
18597
|
}
|
|
18301
|
-
const chunksPath =
|
|
18598
|
+
const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
|
|
18302
18599
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18303
18600
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18304
18601
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18354,20 +18651,20 @@ function computeManifestHash(manifest) {
|
|
|
18354
18651
|
function writeBlob(sourcePath, destinationPath) {
|
|
18355
18652
|
if (existsSync26(destinationPath)) return;
|
|
18356
18653
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18357
|
-
const temporaryDir = mkdtempSync7(
|
|
18358
|
-
const temporaryPath =
|
|
18654
|
+
const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18655
|
+
const temporaryPath = join40(temporaryDir, "blob");
|
|
18359
18656
|
try {
|
|
18360
18657
|
copyFileSync7(sourcePath, temporaryPath);
|
|
18361
18658
|
renameWithRetry8(temporaryPath, destinationPath);
|
|
18362
18659
|
} finally {
|
|
18363
|
-
|
|
18660
|
+
rmSync16(temporaryDir, { recursive: true, force: true });
|
|
18364
18661
|
}
|
|
18365
18662
|
}
|
|
18366
18663
|
function publishBlobFor(absolutePath, digests) {
|
|
18367
18664
|
return {
|
|
18368
18665
|
sourcePath: absolutePath,
|
|
18369
18666
|
sha256: planFileSha256(absolutePath, digests),
|
|
18370
|
-
sizeBytes:
|
|
18667
|
+
sizeBytes: statSync11(absolutePath).size
|
|
18371
18668
|
};
|
|
18372
18669
|
}
|
|
18373
18670
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18391,7 +18688,7 @@ async function putTasks(tasks) {
|
|
|
18391
18688
|
if (failure) throw failure.reason;
|
|
18392
18689
|
}
|
|
18393
18690
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18394
|
-
const executionPlanPath =
|
|
18691
|
+
const executionPlanPath = join40(executionPlanDir, "plan.json");
|
|
18395
18692
|
if (!existsSync26(executionPlanPath)) {
|
|
18396
18693
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18397
18694
|
}
|
|
@@ -18419,7 +18716,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18419
18716
|
}
|
|
18420
18717
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18421
18718
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18422
|
-
const encoder = readJsonFile(
|
|
18719
|
+
const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18423
18720
|
const files = listFiles(executionPlanDir);
|
|
18424
18721
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18425
18722
|
for (const file of files) {
|
|
@@ -18480,20 +18777,20 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18480
18777
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18481
18778
|
}
|
|
18482
18779
|
mkdirSync16(dirname16(planV2Dir), { recursive: true });
|
|
18483
|
-
const tempDir = mkdtempSync7(
|
|
18780
|
+
const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
|
|
18484
18781
|
try {
|
|
18485
18782
|
for (const blob of publication.blobs) {
|
|
18486
18783
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18487
18784
|
}
|
|
18488
18785
|
writeFileSync11(
|
|
18489
|
-
|
|
18786
|
+
join40(tempDir, "plan.json"),
|
|
18490
18787
|
canonicalJsonStringify(publication.manifest),
|
|
18491
18788
|
"utf-8"
|
|
18492
18789
|
);
|
|
18493
18790
|
renameWithRetry8(tempDir, planV2Dir);
|
|
18494
18791
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18495
18792
|
} catch (error) {
|
|
18496
|
-
|
|
18793
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18497
18794
|
throw error;
|
|
18498
18795
|
}
|
|
18499
18796
|
}
|
|
@@ -18533,7 +18830,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18533
18830
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18534
18831
|
}
|
|
18535
18832
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18536
|
-
const stagingRoot = mkdtempSync7(
|
|
18833
|
+
const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
18537
18834
|
const digests = /* @__PURE__ */ new Map();
|
|
18538
18835
|
try {
|
|
18539
18836
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18544,7 +18841,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18544
18841
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18545
18842
|
whileAudioMixes: () => putBlobs(
|
|
18546
18843
|
publisher,
|
|
18547
|
-
listFiles(
|
|
18844
|
+
listFiles(join40(stagingRoot, "compiled")).map(
|
|
18548
18845
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18549
18846
|
)
|
|
18550
18847
|
),
|
|
@@ -18560,7 +18857,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18560
18857
|
}
|
|
18561
18858
|
throw error;
|
|
18562
18859
|
} finally {
|
|
18563
|
-
|
|
18860
|
+
rmSync16(stagingRoot, { recursive: true, force: true });
|
|
18564
18861
|
}
|
|
18565
18862
|
}
|
|
18566
18863
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -18576,7 +18873,7 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
18576
18873
|
function resultFromManifest(planV2Dir, manifest) {
|
|
18577
18874
|
return {
|
|
18578
18875
|
planDir: planV2Dir,
|
|
18579
|
-
manifestPath:
|
|
18876
|
+
manifestPath: join40(planV2Dir, "plan.json"),
|
|
18580
18877
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
18581
18878
|
planHash: manifest.planHash,
|
|
18582
18879
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -18712,7 +19009,7 @@ function parsePlanV2Manifest(value) {
|
|
|
18712
19009
|
return parsed;
|
|
18713
19010
|
}
|
|
18714
19011
|
function readPlanV2Manifest(planV2Dir) {
|
|
18715
|
-
const manifestPath =
|
|
19012
|
+
const manifestPath = join40(planV2Dir, "plan.json");
|
|
18716
19013
|
if (!existsSync26(manifestPath)) {
|
|
18717
19014
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18718
19015
|
}
|
|
@@ -18735,7 +19032,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18735
19032
|
if (!existsSync26(sourcePath)) {
|
|
18736
19033
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18737
19034
|
}
|
|
18738
|
-
const stats =
|
|
19035
|
+
const stats = statSync11(sourcePath);
|
|
18739
19036
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
18740
19037
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
18741
19038
|
}
|
|
@@ -18758,16 +19055,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18758
19055
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
18759
19056
|
}));
|
|
18760
19057
|
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18761
|
-
const tempDir = mkdtempSync7(
|
|
19058
|
+
const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
|
|
18762
19059
|
try {
|
|
18763
19060
|
for (const { artifact, sourcePath } of verified) {
|
|
18764
|
-
const destinationPath =
|
|
19061
|
+
const destinationPath = join40(tempDir, ...artifact.path.split("/"));
|
|
18765
19062
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18766
19063
|
copyFileSync7(sourcePath, destinationPath);
|
|
18767
19064
|
}
|
|
18768
19065
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
18769
19066
|
writeFileSync11(
|
|
18770
|
-
|
|
19067
|
+
join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
18771
19068
|
canonicalJsonStringify({
|
|
18772
19069
|
manifest,
|
|
18773
19070
|
target,
|
|
@@ -18777,7 +19074,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18777
19074
|
);
|
|
18778
19075
|
renameWithRetry8(tempDir, destinationDir);
|
|
18779
19076
|
} catch (error) {
|
|
18780
|
-
|
|
19077
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18781
19078
|
throw error;
|
|
18782
19079
|
}
|
|
18783
19080
|
return {
|
|
@@ -18790,7 +19087,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18790
19087
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
18791
19088
|
// before the container change carries the legacy name, and materializing it
|
|
18792
19089
|
// under the new one would point at a file that was never written.
|
|
18793
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19090
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
|
|
18794
19091
|
};
|
|
18795
19092
|
}
|
|
18796
19093
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -18809,7 +19106,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
18809
19106
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
18810
19107
|
}
|
|
18811
19108
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
18812
|
-
const markerPath =
|
|
19109
|
+
const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
18813
19110
|
if (!existsSync26(markerPath)) return null;
|
|
18814
19111
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
18815
19112
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -18827,8 +19124,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18827
19124
|
assertRemoteSources(remoteSources, required);
|
|
18828
19125
|
for (const artifact of required) {
|
|
18829
19126
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
18830
|
-
const path =
|
|
18831
|
-
if (!existsSync26(path) ||
|
|
19127
|
+
const path = join40(planDir, ...artifact.path.split("/"));
|
|
19128
|
+
if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
|
|
18832
19129
|
throw new PlanV2IntegrityError(
|
|
18833
19130
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
18834
19131
|
);
|
|
@@ -18841,9 +19138,9 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18841
19138
|
}
|
|
18842
19139
|
|
|
18843
19140
|
// src/services/distributed/planV2Execution.ts
|
|
18844
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19141
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
|
|
18845
19142
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
18846
|
-
import { join as
|
|
19143
|
+
import { join as join43 } from "node:path";
|
|
18847
19144
|
|
|
18848
19145
|
// src/services/distributed/assemble.ts
|
|
18849
19146
|
import {
|
|
@@ -18853,11 +19150,11 @@ import {
|
|
|
18853
19150
|
mkdtempSync as mkdtempSync8,
|
|
18854
19151
|
readFileSync as readFileSync14,
|
|
18855
19152
|
readdirSync as readdirSync10,
|
|
18856
|
-
rmSync as
|
|
18857
|
-
statSync as
|
|
19153
|
+
rmSync as rmSync17,
|
|
19154
|
+
statSync as statSync12,
|
|
18858
19155
|
writeFileSync as writeFileSync12
|
|
18859
19156
|
} from "node:fs";
|
|
18860
|
-
import { dirname as dirname17, join as
|
|
19157
|
+
import { dirname as dirname17, join as join41 } from "node:path";
|
|
18861
19158
|
import {
|
|
18862
19159
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
18863
19160
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -18893,7 +19190,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18893
19190
|
outputPath,
|
|
18894
19191
|
durationMs: Date.now() - start,
|
|
18895
19192
|
framesEncoded: plan2.totalFrames,
|
|
18896
|
-
fileSize:
|
|
19193
|
+
fileSize: statSync12(outputPath).size
|
|
18897
19194
|
};
|
|
18898
19195
|
}
|
|
18899
19196
|
const format = plan2.dimensions.format;
|
|
@@ -18921,7 +19218,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18921
19218
|
};
|
|
18922
19219
|
try {
|
|
18923
19220
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
18924
|
-
const finalPath =
|
|
19221
|
+
const finalPath = join41(workDir, `output.${format}`);
|
|
18925
19222
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
18926
19223
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
18927
19224
|
renameWithRetry9(finalPath, outputPath);
|
|
@@ -18932,7 +19229,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18932
19229
|
});
|
|
18933
19230
|
} finally {
|
|
18934
19231
|
try {
|
|
18935
|
-
|
|
19232
|
+
rmSync17(workDir, { recursive: true, force: true });
|
|
18936
19233
|
} catch (err) {
|
|
18937
19234
|
log.warn("[assemble] failed to remove work dir", {
|
|
18938
19235
|
workDir,
|
|
@@ -18940,7 +19237,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18940
19237
|
});
|
|
18941
19238
|
}
|
|
18942
19239
|
}
|
|
18943
|
-
const fileSize = existsSync27(outputPath) ?
|
|
19240
|
+
const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
|
|
18944
19241
|
return {
|
|
18945
19242
|
outputPath,
|
|
18946
19243
|
durationMs: Date.now() - start,
|
|
@@ -18949,8 +19246,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18949
19246
|
};
|
|
18950
19247
|
}
|
|
18951
19248
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
18952
|
-
const planJsonPath =
|
|
18953
|
-
const chunksJsonPath =
|
|
19249
|
+
const planJsonPath = join41(planDir, "plan.json");
|
|
19250
|
+
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
18954
19251
|
if (!existsSync27(planJsonPath)) {
|
|
18955
19252
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
18956
19253
|
}
|
|
@@ -18998,7 +19295,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
18998
19295
|
return args;
|
|
18999
19296
|
}
|
|
19000
19297
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
19001
|
-
const cfrOutputPath =
|
|
19298
|
+
const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
|
|
19002
19299
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
19003
19300
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
19004
19301
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -19025,7 +19322,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19025
19322
|
);
|
|
19026
19323
|
return;
|
|
19027
19324
|
}
|
|
19028
|
-
const paddedAudioPath =
|
|
19325
|
+
const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
|
|
19029
19326
|
const padTrimResult = await ctx.timed(
|
|
19030
19327
|
"audioMs",
|
|
19031
19328
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -19065,7 +19362,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19065
19362
|
}
|
|
19066
19363
|
}
|
|
19067
19364
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
19068
|
-
const listPath =
|
|
19365
|
+
const listPath = join41(workDir, "concat-list.txt");
|
|
19069
19366
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
19070
19367
|
writeFileSync12(listPath, `${body}
|
|
19071
19368
|
`, "utf-8");
|
|
@@ -19103,11 +19400,11 @@ function stitchesSeparateAudio(planDir) {
|
|
|
19103
19400
|
return lockedAudio(planDir).separateAudio === true;
|
|
19104
19401
|
}
|
|
19105
19402
|
function planFormat(planDir) {
|
|
19106
|
-
const plan2 = JSON.parse(readFileSync14(
|
|
19403
|
+
const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
|
|
19107
19404
|
return plan2.dimensions.format;
|
|
19108
19405
|
}
|
|
19109
19406
|
function lockedAudio(planDir) {
|
|
19110
|
-
const encoderJsonPath =
|
|
19407
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19111
19408
|
if (!existsSync27(encoderJsonPath)) return {};
|
|
19112
19409
|
return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
|
|
19113
19410
|
}
|
|
@@ -19117,7 +19414,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
19117
19414
|
`[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
|
|
19118
19415
|
);
|
|
19119
19416
|
}
|
|
19120
|
-
const encoderJsonPath =
|
|
19417
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19121
19418
|
if (!existsSync27(encoderJsonPath)) {
|
|
19122
19419
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
19123
19420
|
}
|
|
@@ -19130,11 +19427,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19130
19427
|
return encoderJson;
|
|
19131
19428
|
}
|
|
19132
19429
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19133
|
-
if (existsSync27(outputPath))
|
|
19430
|
+
if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
|
|
19134
19431
|
mkdirSync17(outputPath, { recursive: true });
|
|
19135
19432
|
let globalIdx = 0;
|
|
19136
19433
|
for (const chunkDir of chunkPaths) {
|
|
19137
|
-
if (!
|
|
19434
|
+
if (!statSync12(chunkDir).isDirectory()) {
|
|
19138
19435
|
throw new Error(
|
|
19139
19436
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
19140
19437
|
);
|
|
@@ -19144,8 +19441,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19144
19441
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
19145
19442
|
}
|
|
19146
19443
|
for (const frame of frames) {
|
|
19147
|
-
const dst =
|
|
19148
|
-
cpSync3(
|
|
19444
|
+
const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
19445
|
+
cpSync3(join41(chunkDir, frame), dst);
|
|
19149
19446
|
globalIdx += 1;
|
|
19150
19447
|
}
|
|
19151
19448
|
}
|
|
@@ -19155,13 +19452,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19155
19452
|
);
|
|
19156
19453
|
}
|
|
19157
19454
|
if (audioPath !== null && existsSync27(audioPath)) {
|
|
19158
|
-
const sidecar =
|
|
19455
|
+
const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
|
|
19159
19456
|
cpSync3(audioPath, sidecar);
|
|
19160
19457
|
}
|
|
19161
19458
|
let fileSize = 0;
|
|
19162
19459
|
for (const name of readdirSync10(outputPath)) {
|
|
19163
19460
|
try {
|
|
19164
|
-
fileSize +=
|
|
19461
|
+
fileSize += statSync12(join41(outputPath, name)).size;
|
|
19165
19462
|
} catch {
|
|
19166
19463
|
}
|
|
19167
19464
|
}
|
|
@@ -19175,9 +19472,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19175
19472
|
|
|
19176
19473
|
// src/services/distributed/renderChunk.ts
|
|
19177
19474
|
import { randomBytes } from "node:crypto";
|
|
19178
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as
|
|
19475
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync18, writeFileSync as writeFileSync13 } from "node:fs";
|
|
19179
19476
|
import { cpus as cpus3 } from "node:os";
|
|
19180
|
-
import { join as
|
|
19477
|
+
import { join as join42 } from "node:path";
|
|
19181
19478
|
import {
|
|
19182
19479
|
assertSwiftShader,
|
|
19183
19480
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -19296,7 +19593,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
19296
19593
|
if (kind === "file") return sha256Hex(readFileSync15(outputPath));
|
|
19297
19594
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19298
19595
|
const lines = entries.map(
|
|
19299
|
-
(name) => `${name}\0${sha256Hex(readFileSync15(
|
|
19596
|
+
(name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
|
|
19300
19597
|
);
|
|
19301
19598
|
return sha256Hex(lines.join("\0"));
|
|
19302
19599
|
}
|
|
@@ -19313,9 +19610,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19313
19610
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19314
19611
|
const start = Date.now();
|
|
19315
19612
|
const log = defaultLogger;
|
|
19316
|
-
const planJsonPath =
|
|
19317
|
-
const encoderJsonPath =
|
|
19318
|
-
const chunksJsonPath =
|
|
19613
|
+
const planJsonPath = join42(planDir, "plan.json");
|
|
19614
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19615
|
+
const chunksJsonPath = join42(planDir, "meta", "chunks.json");
|
|
19319
19616
|
if (!existsSync28(planJsonPath)) {
|
|
19320
19617
|
throw new RenderChunkValidationError(
|
|
19321
19618
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19341,7 +19638,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19341
19638
|
}
|
|
19342
19639
|
const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19343
19640
|
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
19344
|
-
const videosJsonPath =
|
|
19641
|
+
const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19345
19642
|
let planVideos = null;
|
|
19346
19643
|
if (existsSync28(videosJsonPath)) {
|
|
19347
19644
|
try {
|
|
@@ -19373,7 +19670,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19373
19670
|
}
|
|
19374
19671
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19375
19672
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19376
|
-
const compiledDir =
|
|
19673
|
+
const compiledDir = join42(planDir, "compiled");
|
|
19377
19674
|
if (!existsSync28(compiledDir)) {
|
|
19378
19675
|
throw new RenderChunkValidationError(
|
|
19379
19676
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19431,7 +19728,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19431
19728
|
};
|
|
19432
19729
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19433
19730
|
mkdirSync18(workDir, { recursive: true });
|
|
19434
|
-
const framesDir =
|
|
19731
|
+
const framesDir = join42(workDir, "captured-frames");
|
|
19435
19732
|
mkdirSync18(framesDir, { recursive: true });
|
|
19436
19733
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19437
19734
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
@@ -19456,7 +19753,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19456
19753
|
dimensions: plan2.dimensions,
|
|
19457
19754
|
encoder,
|
|
19458
19755
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19459
|
-
html: readFileSync15(
|
|
19756
|
+
html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
|
|
19460
19757
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19461
19758
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19462
19759
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19465,15 +19762,17 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19465
19762
|
deferredVideos.filter((video) => remoteVideoIds.has(video.videoId))
|
|
19466
19763
|
)
|
|
19467
19764
|
});
|
|
19765
|
+
const captureFrames = effectiveChunkFrames(framesInChunk, slice.captureCost);
|
|
19468
19766
|
const chunkWorkerCount = resolveChunkCaptureWorkers(
|
|
19469
|
-
|
|
19470
|
-
calculateOptimalWorkers2(
|
|
19767
|
+
captureFrames,
|
|
19768
|
+
calculateOptimalWorkers2(captureFrames, void 0, cfg),
|
|
19471
19769
|
options?.maxCaptureWorkers
|
|
19472
19770
|
);
|
|
19473
19771
|
let session = null;
|
|
19474
19772
|
let outputKind;
|
|
19475
19773
|
let framesEncoded = 0;
|
|
19476
19774
|
let sessionBootMs = 0;
|
|
19775
|
+
let videoExtractMs = 0;
|
|
19477
19776
|
let captureStageMs = 0;
|
|
19478
19777
|
let encodeStageMs = 0;
|
|
19479
19778
|
let captureMode;
|
|
@@ -19539,6 +19838,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19539
19838
|
session = null;
|
|
19540
19839
|
}
|
|
19541
19840
|
}
|
|
19841
|
+
const extractStarted = Date.now();
|
|
19542
19842
|
const deferredVideoFrames = planVideos ? extractDeferredVideoFramesForChunk({
|
|
19543
19843
|
planDir,
|
|
19544
19844
|
planVideos,
|
|
@@ -19548,6 +19848,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19548
19848
|
cfg,
|
|
19549
19849
|
remoteSources,
|
|
19550
19850
|
shownBoxes
|
|
19851
|
+
}).then((frames) => {
|
|
19852
|
+
videoExtractMs = Date.now() - extractStarted;
|
|
19853
|
+
return frames;
|
|
19551
19854
|
}) : Promise.resolve([]);
|
|
19552
19855
|
extraction = deferredVideoFrames;
|
|
19553
19856
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
|
|
@@ -19581,7 +19884,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19581
19884
|
if (isPngSequence) {
|
|
19582
19885
|
if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
|
|
19583
19886
|
} else {
|
|
19584
|
-
const outDir =
|
|
19887
|
+
const outDir = join42(outputChunkPath, "..");
|
|
19585
19888
|
if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
|
|
19586
19889
|
}
|
|
19587
19890
|
const encodeInput = {
|
|
@@ -19685,7 +19988,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19685
19988
|
return observedChunkCaptureMode(capturePerfs);
|
|
19686
19989
|
},
|
|
19687
19990
|
resetForScreenshotRetry: () => {
|
|
19688
|
-
|
|
19991
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19689
19992
|
mkdirSync18(framesDir, { recursive: true });
|
|
19690
19993
|
capturePerfs.length = 0;
|
|
19691
19994
|
captureAttempts.length = 0;
|
|
@@ -19728,8 +20031,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19728
20031
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
19729
20032
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
19730
20033
|
});
|
|
19731
|
-
|
|
19732
|
-
|
|
20034
|
+
rmSync18(videoOnlyPath, { force: true });
|
|
20035
|
+
rmSync18(audioScratchPath, { force: true });
|
|
19733
20036
|
}
|
|
19734
20037
|
encodeStageMs = Date.now() - encodeStarted;
|
|
19735
20038
|
} finally {
|
|
@@ -19765,6 +20068,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19765
20068
|
sessionBootMs,
|
|
19766
20069
|
captureStageMs,
|
|
19767
20070
|
encodeStageMs,
|
|
20071
|
+
// Wall time of the chunk's own video frame extraction; it overlaps captureStageMs, so do not add them.
|
|
20072
|
+
videoExtractMs,
|
|
19768
20073
|
workers: chunkWorkerCount,
|
|
19769
20074
|
captureMode,
|
|
19770
20075
|
// Device-pixel box each video's frames were extracted to cover; a video not listed kept full size.
|
|
@@ -19777,8 +20082,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19777
20082
|
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
19778
20083
|
`, "utf-8");
|
|
19779
20084
|
try {
|
|
19780
|
-
|
|
19781
|
-
|
|
20085
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20086
|
+
rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19782
20087
|
} catch (err) {
|
|
19783
20088
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
19784
20089
|
workDir,
|
|
@@ -19807,18 +20112,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19807
20112
|
|
|
19808
20113
|
// src/services/distributed/planV2Execution.ts
|
|
19809
20114
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
19810
|
-
const workRoot = mkdtempSync9(
|
|
19811
|
-
const materializedPlanDir =
|
|
20115
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
20116
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19812
20117
|
try {
|
|
19813
20118
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
19814
20119
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
19815
20120
|
} finally {
|
|
19816
|
-
|
|
20121
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19817
20122
|
}
|
|
19818
20123
|
}
|
|
19819
20124
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
19820
|
-
const workRoot = mkdtempSync9(
|
|
19821
|
-
const materializedPlanDir =
|
|
20125
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
20126
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19822
20127
|
try {
|
|
19823
20128
|
const materialized = materializePlanV2Target(
|
|
19824
20129
|
planV2Dir,
|
|
@@ -19833,7 +20138,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
19833
20138
|
options
|
|
19834
20139
|
);
|
|
19835
20140
|
} finally {
|
|
19836
|
-
|
|
20141
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19837
20142
|
}
|
|
19838
20143
|
}
|
|
19839
20144
|
|