@hyperframes/producer 0.8.198 → 0.8.199
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 +134 -145
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +59 -59
- package/dist/index.js +647 -421
- package/dist/public-server.js +489 -263
- package/dist/renderRequest.d.ts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/services/hdrCompositor.d.ts +5 -5
- package/dist/services/hdrFrameWindows.d.ts +23 -0
- package/dist/services/render/renderEtaGuard.d.ts +35 -0
- package/dist/services/render/stages/captureHdrResources.d.ts +5 -17
- package/dist/services/renderOrchestrator.d.ts +5 -5
- 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,
|
|
@@ -2275,7 +2275,7 @@ import {
|
|
|
2275
2275
|
} from "@hyperframes/engine";
|
|
2276
2276
|
|
|
2277
2277
|
// src/services/hdrCompositor.ts
|
|
2278
|
-
import { readSync as readSync2
|
|
2278
|
+
import { readSync as readSync2 } from "fs";
|
|
2279
2279
|
import { join as join6 } from "path";
|
|
2280
2280
|
import {
|
|
2281
2281
|
captureAlphaPng,
|
|
@@ -2328,10 +2328,10 @@ function cropRgb48le(src, srcW, srcH, cropX, cropY, cropW, cropH) {
|
|
|
2328
2328
|
}
|
|
2329
2329
|
function closeHdrVideoFrameSource(source, log) {
|
|
2330
2330
|
try {
|
|
2331
|
-
|
|
2331
|
+
source.windows.close();
|
|
2332
2332
|
} catch (err) {
|
|
2333
|
-
log?.warn("Failed to close HDR raw frame
|
|
2334
|
-
|
|
2333
|
+
log?.warn("Failed to close HDR raw frame windows", {
|
|
2334
|
+
dir: source.dir,
|
|
2335
2335
|
error: err instanceof Error ? err.message : String(err)
|
|
2336
2336
|
});
|
|
2337
2337
|
}
|
|
@@ -2341,7 +2341,7 @@ function resolveHdrVideoFrameIndex(time, startTime, fps, frameCount, loop = fals
|
|
|
2341
2341
|
if (frameIndex < 0 || frameCount < 1) return null;
|
|
2342
2342
|
return loop ? frameIndex % frameCount : Math.min(frameIndex, frameCount - 1);
|
|
2343
2343
|
}
|
|
2344
|
-
function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2344
|
+
async function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2345
2345
|
const frameSource = hdrVideoFrameSources.get(el.id);
|
|
2346
2346
|
const startTime = hdrStartTimes.get(el.id);
|
|
2347
2347
|
if (!frameSource || startTime === void 0 || el.opacity <= 0) {
|
|
@@ -2357,13 +2357,32 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
|
|
|
2357
2357
|
if (effectiveIndex === null) return;
|
|
2358
2358
|
const mediaTime = (frameSource.mediaStart ?? 0) + effectiveIndex / fps;
|
|
2359
2359
|
if (isVideoHiddenBeforeStreamStart(frameSource.videoStreamLeadSeconds, mediaTime)) return;
|
|
2360
|
-
|
|
2360
|
+
if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
|
|
2361
|
+
const lease = await frameSource.windows.lease(effectiveIndex);
|
|
2362
|
+
if (!lease) return;
|
|
2363
|
+
try {
|
|
2364
|
+
blitScratchFrameWithoutYielding(
|
|
2365
|
+
canvas,
|
|
2366
|
+
el,
|
|
2367
|
+
frameSource,
|
|
2368
|
+
lease,
|
|
2369
|
+
width,
|
|
2370
|
+
height,
|
|
2371
|
+
log,
|
|
2372
|
+
sourceTransfer,
|
|
2373
|
+
targetTransfer,
|
|
2374
|
+
hdrPerf
|
|
2375
|
+
);
|
|
2376
|
+
} finally {
|
|
2377
|
+
lease.release();
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function blitScratchFrameWithoutYielding(canvas, el, frameSource, lease, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
|
|
2361
2381
|
try {
|
|
2362
|
-
if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
|
|
2363
2382
|
const bytesRead = timeHdrPhase(
|
|
2364
2383
|
hdrPerf,
|
|
2365
2384
|
"hdrVideoReadDecodeMs",
|
|
2366
|
-
() => readSync2(
|
|
2385
|
+
() => readSync2(lease.fd, frameSource.scratch, 0, frameSource.frameSize, lease.offset)
|
|
2367
2386
|
);
|
|
2368
2387
|
if (bytesRead !== frameSource.frameSize) return;
|
|
2369
2388
|
const hdrRgb = frameSource.scratch;
|
|
@@ -2592,7 +2611,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2592
2611
|
hdrPerf
|
|
2593
2612
|
);
|
|
2594
2613
|
} else {
|
|
2595
|
-
blitHdrVideoLayer(
|
|
2614
|
+
await blitHdrVideoLayer(
|
|
2596
2615
|
canvas,
|
|
2597
2616
|
layer.element,
|
|
2598
2617
|
time,
|
|
@@ -2636,7 +2655,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2636
2655
|
startTime,
|
|
2637
2656
|
localTime: localTime.toFixed(3),
|
|
2638
2657
|
hdrFrameNum: frameNum,
|
|
2639
|
-
|
|
2658
|
+
dir: frameSource?.dir ?? null,
|
|
2640
2659
|
frameCount: frameSource?.frameCount ?? null
|
|
2641
2660
|
});
|
|
2642
2661
|
}
|
|
@@ -2732,18 +2751,8 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
|
|
|
2732
2751
|
}
|
|
2733
2752
|
|
|
2734
2753
|
// 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";
|
|
2754
|
+
import { mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, rmSync as rmSync5, statSync as statSync2, statfsSync } from "node:fs";
|
|
2755
|
+
import { join as join8 } from "node:path";
|
|
2747
2756
|
import {
|
|
2748
2757
|
decodePngToRgb48le,
|
|
2749
2758
|
extractMediaMetadata as extractMediaMetadata2,
|
|
@@ -2757,7 +2766,90 @@ import {
|
|
|
2757
2766
|
runFfmpeg
|
|
2758
2767
|
} from "@hyperframes/engine";
|
|
2759
2768
|
import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
2760
|
-
|
|
2769
|
+
|
|
2770
|
+
// src/services/hdrFrameWindows.ts
|
|
2771
|
+
import { closeSync as closeSync3, constants as constants2, openSync as openSync3, rmSync as rmSync4 } from "node:fs";
|
|
2772
|
+
import { join as join7 } from "node:path";
|
|
2773
|
+
function createHdrFrameWindows(opts) {
|
|
2774
|
+
const { dir, frameSize, windowFrames, maxResident, extract } = opts;
|
|
2775
|
+
let totalFrames = opts.totalFrames;
|
|
2776
|
+
const windows = /* @__PURE__ */ new Map();
|
|
2777
|
+
const drop = (index, w) => {
|
|
2778
|
+
windows.delete(index);
|
|
2779
|
+
if (w.fd >= 0) closeSync3(w.fd);
|
|
2780
|
+
rmSync4(w.path, { force: true });
|
|
2781
|
+
};
|
|
2782
|
+
const open2 = (index) => {
|
|
2783
|
+
const known = windows.get(index);
|
|
2784
|
+
if (known) {
|
|
2785
|
+
windows.delete(index);
|
|
2786
|
+
windows.set(index, known);
|
|
2787
|
+
return known;
|
|
2788
|
+
}
|
|
2789
|
+
for (const [i, w2] of windows) {
|
|
2790
|
+
if (windows.size < maxResident) break;
|
|
2791
|
+
if (w2.pins === 0) drop(i, w2);
|
|
2792
|
+
}
|
|
2793
|
+
const firstFrame = index * windowFrames;
|
|
2794
|
+
const w = {
|
|
2795
|
+
firstFrame,
|
|
2796
|
+
path: join7(dir, `window-${index}.rgb48le`),
|
|
2797
|
+
frames: 0,
|
|
2798
|
+
fd: -1,
|
|
2799
|
+
pins: 0,
|
|
2800
|
+
ready: Promise.resolve()
|
|
2801
|
+
};
|
|
2802
|
+
w.ready = extract({
|
|
2803
|
+
firstFrame,
|
|
2804
|
+
frames: Math.min(windowFrames, totalFrames - firstFrame),
|
|
2805
|
+
path: w.path
|
|
2806
|
+
}).then((produced) => {
|
|
2807
|
+
if (produced < 1) return;
|
|
2808
|
+
w.fd = openSync3(w.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
2809
|
+
w.frames = produced;
|
|
2810
|
+
});
|
|
2811
|
+
windows.set(index, w);
|
|
2812
|
+
return w;
|
|
2813
|
+
};
|
|
2814
|
+
return {
|
|
2815
|
+
get totalFrames() {
|
|
2816
|
+
return totalFrames;
|
|
2817
|
+
},
|
|
2818
|
+
async lease(frameIndex) {
|
|
2819
|
+
for (; ; ) {
|
|
2820
|
+
if (totalFrames < 1) return null;
|
|
2821
|
+
const index = Math.min(Math.max(0, frameIndex), totalFrames - 1);
|
|
2822
|
+
const key = Math.floor(index / windowFrames);
|
|
2823
|
+
const w = open2(key);
|
|
2824
|
+
w.pins += 1;
|
|
2825
|
+
try {
|
|
2826
|
+
await w.ready;
|
|
2827
|
+
} catch (error) {
|
|
2828
|
+
w.pins -= 1;
|
|
2829
|
+
windows.delete(key);
|
|
2830
|
+
throw error;
|
|
2831
|
+
}
|
|
2832
|
+
const local = index - w.firstFrame;
|
|
2833
|
+
if (local < w.frames) {
|
|
2834
|
+
return {
|
|
2835
|
+
fd: w.fd,
|
|
2836
|
+
offset: local * frameSize,
|
|
2837
|
+
release: () => {
|
|
2838
|
+
w.pins -= 1;
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
w.pins -= 1;
|
|
2843
|
+
totalFrames = Math.min(totalFrames, w.firstFrame + w.frames);
|
|
2844
|
+
}
|
|
2845
|
+
},
|
|
2846
|
+
close() {
|
|
2847
|
+
for (const [i, w] of [...windows]) drop(i, w);
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// src/services/render/stages/captureHdrResources.ts
|
|
2761
2853
|
function tempDirSafePrefix(id) {
|
|
2762
2854
|
const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
|
|
2763
2855
|
return safe || "video";
|
|
@@ -2802,10 +2894,16 @@ async function probeHdrExtractionDims(args) {
|
|
|
2802
2894
|
const stacking = await queryElementStacking(domSession.page, nativeHdrIds);
|
|
2803
2895
|
for (const el of stacking) {
|
|
2804
2896
|
if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !prep.hdrExtractionDims.has(el.id)) {
|
|
2805
|
-
prep.hdrExtractionDims.set(el.id, {
|
|
2897
|
+
prep.hdrExtractionDims.set(el.id, {
|
|
2898
|
+
width: el.layoutWidth,
|
|
2899
|
+
height: el.layoutHeight
|
|
2900
|
+
});
|
|
2806
2901
|
}
|
|
2807
2902
|
if (el.isHdr && nativeHdrImageIds.has(el.id) && !prep.hdrImageFitInfo.has(el.id)) {
|
|
2808
|
-
prep.hdrImageFitInfo.set(el.id, {
|
|
2903
|
+
prep.hdrImageFitInfo.set(el.id, {
|
|
2904
|
+
fit: el.objectFit,
|
|
2905
|
+
position: el.objectPosition
|
|
2906
|
+
});
|
|
2809
2907
|
}
|
|
2810
2908
|
}
|
|
2811
2909
|
}
|
|
@@ -2824,23 +2922,21 @@ async function probeHdrExtractionDims(args) {
|
|
|
2824
2922
|
const retryStacking = await queryElementStacking(domSession.page, nativeHdrIds);
|
|
2825
2923
|
for (const el of retryStacking) {
|
|
2826
2924
|
if (el.id === imageId && el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0) {
|
|
2827
|
-
prep.hdrExtractionDims.set(el.id, {
|
|
2925
|
+
prep.hdrExtractionDims.set(el.id, {
|
|
2926
|
+
width: el.layoutWidth,
|
|
2927
|
+
height: el.layoutHeight
|
|
2928
|
+
});
|
|
2828
2929
|
if (!prep.hdrImageFitInfo.has(el.id)) {
|
|
2829
|
-
prep.hdrImageFitInfo.set(el.id, {
|
|
2930
|
+
prep.hdrImageFitInfo.set(el.id, {
|
|
2931
|
+
fit: el.objectFit,
|
|
2932
|
+
position: el.objectPosition
|
|
2933
|
+
});
|
|
2830
2934
|
}
|
|
2831
2935
|
break;
|
|
2832
2936
|
}
|
|
2833
2937
|
}
|
|
2834
2938
|
}
|
|
2835
2939
|
}
|
|
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
2940
|
var HDR_EXTRACTION_HEADROOM_FRACTION = 0.9;
|
|
2845
2941
|
var HDR_EXTRACTION_CGROUP_BUDGET_FRACTION = 0.5;
|
|
2846
2942
|
var HDR_EXTRACTION_WARN_BYTES = 1e10;
|
|
@@ -2904,24 +3000,22 @@ function resolveHdrExtractionWindow(video, compositionDuration, metadata) {
|
|
|
2904
3000
|
}
|
|
2905
3001
|
return window2;
|
|
2906
3002
|
}
|
|
2907
|
-
function cleanupHdrFrameDirectory(frameDir,
|
|
3003
|
+
function cleanupHdrFrameDirectory(frameDir, log) {
|
|
2908
3004
|
if (process.env.KEEP_TEMP === "1") return;
|
|
2909
3005
|
try {
|
|
2910
|
-
|
|
3006
|
+
rmSync5(frameDir, { recursive: true, force: true });
|
|
2911
3007
|
} catch (err) {
|
|
2912
3008
|
log?.warn("Failed to clean up HDR raw frame directory", {
|
|
2913
3009
|
frameDir,
|
|
2914
|
-
rawPath,
|
|
2915
3010
|
error: err instanceof Error ? err.message : String(err)
|
|
2916
3011
|
});
|
|
2917
3012
|
}
|
|
2918
3013
|
}
|
|
2919
3014
|
function cleanupHdrVideoFrameSource(source, log) {
|
|
2920
3015
|
closeHdrVideoFrameSource(source, log);
|
|
2921
|
-
cleanupHdrFrameDirectory(source.dir,
|
|
3016
|
+
cleanupHdrFrameDirectory(source.dir, log);
|
|
2922
3017
|
}
|
|
2923
|
-
function assertHdrExtractionDiskHeadroom(framesDir,
|
|
2924
|
-
const estimatedBytes = estimateHdrExtractionBytes(plannedVideos, fps);
|
|
3018
|
+
function assertHdrExtractionDiskHeadroom(framesDir, estimatedBytes, log) {
|
|
2925
3019
|
const configuredBudget = resolveHdrExtractionBudgetBytes(
|
|
2926
3020
|
process.env[HDR_EXTRACTION_MAX_BYTES_ENV]
|
|
2927
3021
|
);
|
|
@@ -2955,14 +3049,29 @@ function assertHdrExtractionDiskHeadroom(framesDir, plannedVideos, fps, log) {
|
|
|
2955
3049
|
budgetBytes: resolveHdrExtractionActiveBudgetBytes(configuredBudget, freeBytes)
|
|
2956
3050
|
};
|
|
2957
3051
|
}
|
|
3052
|
+
var DEFAULT_HDR_WINDOW_BYTES = 512 * BYTES_PER_MIB;
|
|
3053
|
+
function hdrWindowFrames(frameSize, windowBytes) {
|
|
3054
|
+
return Math.max(8, Math.floor(windowBytes / frameSize));
|
|
3055
|
+
}
|
|
3056
|
+
async function probeFirstTick(srcPath, mediaStart, fps, runFfmpegImpl, abortSignal) {
|
|
3057
|
+
const args = ["-ss", String(mediaStart), "-i", srcPath, "-frames:v", "1"];
|
|
3058
|
+
const result = await runFfmpegImpl([...args, ...["-vf", "showinfo", "-f", "null", "-"]], {
|
|
3059
|
+
signal: abortSignal
|
|
3060
|
+
});
|
|
3061
|
+
const match = /pts_time:(-?[\d.]+)/.exec(result.stderr);
|
|
3062
|
+
return match ? Math.max(0, Math.floor(Number(match[1]) * fps + 0.5)) : 0;
|
|
3063
|
+
}
|
|
2958
3064
|
async function extractHdrVideoFrames(args) {
|
|
2959
3065
|
const { job, log, framesDir, composition, prep, width, height, abortSignal, hdrDiagnostics } = args;
|
|
3066
|
+
const maxResident = Math.max(2, args.maxResidentWindows ?? 2);
|
|
3067
|
+
const windowBytes = args.windowBytes ?? DEFAULT_HDR_WINDOW_BYTES;
|
|
2960
3068
|
const runFfmpegImpl = args.runFfmpegImpl ?? runFfmpeg;
|
|
2961
3069
|
const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata2;
|
|
2962
3070
|
const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
|
|
3071
|
+
const fps = fpsToNumber4(job.config.fps);
|
|
2963
3072
|
const out = /* @__PURE__ */ new Map();
|
|
2964
3073
|
mkdirSync2(framesDir, { recursive: true });
|
|
2965
|
-
|
|
3074
|
+
let scratchBytes = 0;
|
|
2966
3075
|
const extractionWindows = /* @__PURE__ */ new Map();
|
|
2967
3076
|
for (const [videoId, srcPath] of prep.hdrVideoSrcPaths) {
|
|
2968
3077
|
const video = composition.videos.find((v) => v.id === videoId);
|
|
@@ -2983,16 +3092,13 @@ async function extractHdrVideoFrames(args) {
|
|
|
2983
3092
|
videoStreamLeadSeconds: metadata.videoStreamLeadSeconds
|
|
2984
3093
|
});
|
|
2985
3094
|
prep.hdrVideoStartTimes.set(videoId, window2.compositionStart);
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
height: dims.height
|
|
2990
|
-
});
|
|
3095
|
+
const frameSize = dims.width * dims.height * 6;
|
|
3096
|
+
const frames = window2.finalFrameOnly ? 1 : Math.ceil(window2.durationSeconds * fps);
|
|
3097
|
+
scratchBytes += Math.min(frames, maxResident * hdrWindowFrames(frameSize, windowBytes)) * frameSize;
|
|
2991
3098
|
}
|
|
2992
3099
|
const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
|
|
2993
3100
|
framesDir,
|
|
2994
|
-
|
|
2995
|
-
fpsToNumber4(job.config.fps),
|
|
3101
|
+
scratchBytes,
|
|
2996
3102
|
log
|
|
2997
3103
|
);
|
|
2998
3104
|
const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
|
|
@@ -3004,79 +3110,79 @@ async function extractHdrVideoFrames(args) {
|
|
|
3004
3110
|
if (!video || !planned) continue;
|
|
3005
3111
|
const { window: window2 } = planned;
|
|
3006
3112
|
mkdirSync2(framesDir, { recursive: true });
|
|
3007
|
-
const frameDir = mkdtempSync2(
|
|
3113
|
+
const frameDir = mkdtempSync2(join8(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
|
|
3008
3114
|
createdFrameDirs.add(frameDir);
|
|
3009
3115
|
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
3116
|
const frameSize = dims.width * dims.height * 6;
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3117
|
+
const extractionMediaStart = window2.extractionMediaStart ?? window2.mediaStart;
|
|
3118
|
+
const scale = `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`;
|
|
3119
|
+
const firstTick = window2.finalFrameOnly ? 0 : await probeFirstTick(srcPath, extractionMediaStart, fps, runFfmpegImpl, abortSignal);
|
|
3120
|
+
const totalFrames = window2.finalFrameOnly ? 1 : Math.max(1, Math.ceil(window2.durationSeconds * fps - firstTick - 1e-9)) + Number(video.loop);
|
|
3121
|
+
const windowFrames = Math.min(totalFrames, hdrWindowFrames(frameSize, windowBytes));
|
|
3122
|
+
const windows = createHdrFrameWindows({
|
|
3123
|
+
dir: frameDir,
|
|
3124
|
+
frameSize,
|
|
3125
|
+
totalFrames,
|
|
3126
|
+
windowFrames,
|
|
3127
|
+
maxResident,
|
|
3128
|
+
extract: async (spec) => {
|
|
3129
|
+
const ffmpegArgs = window2.finalFrameOnly ? (
|
|
3130
|
+
// Decode before seeking for the one-frame path. Input-side seeking can
|
|
3131
|
+
// return zero frames for valid unindexed/negative-base transports.
|
|
3132
|
+
["-i", srcPath, "-ss", String(extractionMediaStart), "-frames:v", "1", "-vf", scale]
|
|
3133
|
+
) : hdrWindowFfmpegArgs({
|
|
3134
|
+
srcPath,
|
|
3135
|
+
mediaStart: extractionMediaStart,
|
|
3136
|
+
fps,
|
|
3137
|
+
fpsArg: fpsToFfmpegArg(job.config.fps),
|
|
3138
|
+
firstTick,
|
|
3139
|
+
firstFrame: spec.firstFrame,
|
|
3140
|
+
frames: spec.frames,
|
|
3141
|
+
scale
|
|
3142
|
+
});
|
|
3143
|
+
const result = await runFfmpegImpl(
|
|
3144
|
+
[...ffmpegArgs, "-pix_fmt", "rgb48le", "-f", "rawvideo", "-y", spec.path],
|
|
3145
|
+
{ signal: abortSignal }
|
|
3061
3146
|
);
|
|
3147
|
+
if (!result.success) {
|
|
3148
|
+
hdrDiagnostics.videoExtractionFailures += 1;
|
|
3149
|
+
log.error("HDR frame extraction failed; aborting render", {
|
|
3150
|
+
videoId,
|
|
3151
|
+
srcPath,
|
|
3152
|
+
firstFrame: spec.firstFrame,
|
|
3153
|
+
stderr: result.stderr.slice(-400)
|
|
3154
|
+
});
|
|
3155
|
+
throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
|
|
3156
|
+
error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
|
|
3157
|
+
failureReason: result.failureReason
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
return Math.min(Math.floor(statSync2(spec.path).size / frameSize), spec.frames);
|
|
3062
3161
|
}
|
|
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);
|
|
3162
|
+
});
|
|
3163
|
+
const first = await windows.lease(0);
|
|
3164
|
+
if (!first) {
|
|
3165
|
+
hdrDiagnostics.videoExtractionFailures += 1;
|
|
3166
|
+
throw new Error(
|
|
3167
|
+
`HDR frame extraction produced no frames for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
|
|
3168
|
+
);
|
|
3079
3169
|
}
|
|
3170
|
+
first.release();
|
|
3171
|
+
if (video.loop) (await windows.lease(totalFrames - 1))?.release();
|
|
3172
|
+
out.set(videoId, {
|
|
3173
|
+
dir: frameDir,
|
|
3174
|
+
windows,
|
|
3175
|
+
width: dims.width,
|
|
3176
|
+
height: dims.height,
|
|
3177
|
+
frameSize,
|
|
3178
|
+
get frameCount() {
|
|
3179
|
+
return windows.totalFrames;
|
|
3180
|
+
},
|
|
3181
|
+
scratch: Buffer.allocUnsafe(frameSize),
|
|
3182
|
+
loop: video.loop,
|
|
3183
|
+
mediaStart: extractionMediaStart,
|
|
3184
|
+
videoStreamLeadSeconds: planned.videoStreamLeadSeconds
|
|
3185
|
+
});
|
|
3080
3186
|
}
|
|
3081
3187
|
return { sources: out, estimatedBytes, releaseReservation };
|
|
3082
3188
|
} catch (error) {
|
|
@@ -3085,12 +3191,23 @@ async function extractHdrVideoFrames(args) {
|
|
|
3085
3191
|
createdFrameDirs.delete(source.dir);
|
|
3086
3192
|
}
|
|
3087
3193
|
for (const frameDir of createdFrameDirs) {
|
|
3088
|
-
cleanupHdrFrameDirectory(frameDir,
|
|
3194
|
+
cleanupHdrFrameDirectory(frameDir, log);
|
|
3089
3195
|
}
|
|
3090
3196
|
releaseReservation();
|
|
3091
3197
|
throw error;
|
|
3092
3198
|
}
|
|
3093
3199
|
}
|
|
3200
|
+
function hdrWindowFfmpegArgs(a) {
|
|
3201
|
+
const unit = Math.ceil(a.fps);
|
|
3202
|
+
const seekFrame = a.firstFrame < unit ? 0 : (Math.floor(a.firstFrame / unit) - 1) * unit;
|
|
3203
|
+
const trim = a.firstFrame - seekFrame;
|
|
3204
|
+
const filters = [`fps=${a.fpsArg}:start_time=${(a.firstTick / a.fps).toFixed(6)}`, a.scale];
|
|
3205
|
+
if (trim > 0) filters.push(`trim=start_frame=${trim}`);
|
|
3206
|
+
return [
|
|
3207
|
+
...["-ss", (a.mediaStart + seekFrame / a.fps).toFixed(6), "-i", a.srcPath],
|
|
3208
|
+
...["-vf", filters.join(","), "-frames:v", String(a.frames)]
|
|
3209
|
+
];
|
|
3210
|
+
}
|
|
3094
3211
|
function decodeHdrImageBuffers(args) {
|
|
3095
3212
|
const { log, hdrImageSrcPaths, prep, hdrDiagnostics } = args;
|
|
3096
3213
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3110,7 +3227,11 @@ function decodeHdrImageBuffers(args) {
|
|
|
3110
3227
|
fit,
|
|
3111
3228
|
fitInfo?.position
|
|
3112
3229
|
);
|
|
3113
|
-
out.set(imageId, {
|
|
3230
|
+
out.set(imageId, {
|
|
3231
|
+
data: resampled,
|
|
3232
|
+
width: layout.width,
|
|
3233
|
+
height: layout.height
|
|
3234
|
+
});
|
|
3114
3235
|
} else {
|
|
3115
3236
|
out.set(imageId, {
|
|
3116
3237
|
data: Buffer.from(decoded.data),
|
|
@@ -3222,7 +3343,7 @@ async function captureSceneIntoBuffer(a) {
|
|
|
3222
3343
|
hdrPerf
|
|
3223
3344
|
);
|
|
3224
3345
|
} else {
|
|
3225
|
-
blitHdrVideoLayer(
|
|
3346
|
+
await blitHdrVideoLayer(
|
|
3226
3347
|
sceneBuf,
|
|
3227
3348
|
el,
|
|
3228
3349
|
time,
|
|
@@ -3424,7 +3545,7 @@ function isTargetLossError(error) {
|
|
|
3424
3545
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3425
3546
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
3426
3547
|
import { tmpdir } from "node:os";
|
|
3427
|
-
import { join as
|
|
3548
|
+
import { join as join9 } from "node:path";
|
|
3428
3549
|
import {
|
|
3429
3550
|
DrawElementVerificationError,
|
|
3430
3551
|
captureFrameToBuffer,
|
|
@@ -3586,11 +3707,11 @@ function createDrainFrameGuard(args) {
|
|
|
3586
3707
|
return buf;
|
|
3587
3708
|
}
|
|
3588
3709
|
if (db < verifyMinDb) {
|
|
3589
|
-
const dumpDir = await mkdtemp(
|
|
3710
|
+
const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
|
|
3590
3711
|
if (dumpDir) {
|
|
3591
3712
|
await Promise.all([
|
|
3592
|
-
writeFile(
|
|
3593
|
-
writeFile(
|
|
3713
|
+
writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
|
|
3714
|
+
writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
|
|
3594
3715
|
]).catch(() => {
|
|
3595
3716
|
});
|
|
3596
3717
|
}
|
|
@@ -4039,7 +4160,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4039
4160
|
|
|
4040
4161
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
4041
4162
|
function segmentOutputPath(segmentDir, index) {
|
|
4042
|
-
return
|
|
4163
|
+
return join10(segmentDir, `segment_${String(index).padStart(5, "0")}.mp4`);
|
|
4043
4164
|
}
|
|
4044
4165
|
function segmentEncoderOptions(base, segment) {
|
|
4045
4166
|
return {
|
|
@@ -4115,7 +4236,7 @@ async function captureSegment(ctx, segment) {
|
|
|
4115
4236
|
path: segmentPath,
|
|
4116
4237
|
// Recorded so resume can prove the file on disk is the one that
|
|
4117
4238
|
// finished; absent only in unit tests, whose fake encoder writes none.
|
|
4118
|
-
bytes: existsSync6(segmentPath) ?
|
|
4239
|
+
bytes: existsSync6(segmentPath) ? statSync3(segmentPath).size : 0
|
|
4119
4240
|
});
|
|
4120
4241
|
return encodeResult.durationMs;
|
|
4121
4242
|
} finally {
|
|
@@ -4133,7 +4254,7 @@ var DEFAULT_DEPS = {
|
|
|
4133
4254
|
// is invisible to a test that swaps the engine module after this file has
|
|
4134
4255
|
// been evaluated (bun updates live bindings, not copies).
|
|
4135
4256
|
closeSession: (session) => closeCaptureSession2(session),
|
|
4136
|
-
removeFile: (path) =>
|
|
4257
|
+
removeFile: (path) => rmSync6(path, { force: true })
|
|
4137
4258
|
};
|
|
4138
4259
|
function defaultSessionFactory(input) {
|
|
4139
4260
|
let probeSession = input.probeSession;
|
|
@@ -4300,7 +4421,7 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4300
4421
|
const { log, dedupPerfs, assertNotAborted } = input;
|
|
4301
4422
|
const deps = { ...DEFAULT_DEPS, ...input.deps };
|
|
4302
4423
|
const segments = planSegments(input.totalFrames, input.segmentFrames);
|
|
4303
|
-
const segmentDir = input.segmentDir ??
|
|
4424
|
+
const segmentDir = input.segmentDir ?? join10(input.workDir, "segments");
|
|
4304
4425
|
mkdirSync3(segmentDir, { recursive: true });
|
|
4305
4426
|
const skip = input.completedSegments ?? /* @__PURE__ */ new Set();
|
|
4306
4427
|
const pending = segments.filter((s) => !skip.has(s.index));
|
|
@@ -4371,10 +4492,83 @@ async function runCaptureSegmentedStage(input) {
|
|
|
4371
4492
|
};
|
|
4372
4493
|
}
|
|
4373
4494
|
|
|
4495
|
+
// src/services/render/renderEtaGuard.ts
|
|
4496
|
+
var RENDER_TOO_SLOW = "RENDER_TOO_SLOW";
|
|
4497
|
+
var ETA_MARGIN = 1.2;
|
|
4498
|
+
var MIN_SAMPLE_MS = 6e4;
|
|
4499
|
+
var MIN_SAMPLE_FRAMES = 3;
|
|
4500
|
+
var POLL_MS = 5e3;
|
|
4501
|
+
var RenderTooSlowError = class extends Error {
|
|
4502
|
+
code = RENDER_TOO_SLOW;
|
|
4503
|
+
// The producer server reads these off the error for the wire envelope.
|
|
4504
|
+
// fallow-ignore-next-line unused-class-member
|
|
4505
|
+
owner = "user";
|
|
4506
|
+
// fallow-ignore-next-line unused-class-member
|
|
4507
|
+
retryable = false;
|
|
4508
|
+
publicMetadata;
|
|
4509
|
+
constructor(stage, input) {
|
|
4510
|
+
const { framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs } = input;
|
|
4511
|
+
super(
|
|
4512
|
+
`render too slow: stage ${stage}, ${framesCompleted}/${totalFrames} frames, ${Math.round(msPerFrame)} ms/frame, ETA ${etaMs} ms exceeds maxRenderMs ${maxRenderMs}`
|
|
4513
|
+
);
|
|
4514
|
+
this.name = "RenderTooSlowError";
|
|
4515
|
+
this.publicMetadata = { stage, framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs };
|
|
4516
|
+
}
|
|
4517
|
+
};
|
|
4518
|
+
function checkRenderEta(stage, input) {
|
|
4519
|
+
const { maxRenderMs, elapsedMs, framesCompleted, totalFrames, msPerFrame } = input;
|
|
4520
|
+
if (!maxRenderMs || maxRenderMs <= 0 || !(msPerFrame > 0)) return void 0;
|
|
4521
|
+
const etaMs = Math.round(elapsedMs + (totalFrames - framesCompleted) * msPerFrame);
|
|
4522
|
+
if (etaMs <= maxRenderMs * ETA_MARGIN) return void 0;
|
|
4523
|
+
return new RenderTooSlowError(stage, { ...input, maxRenderMs, etaMs });
|
|
4524
|
+
}
|
|
4525
|
+
function createRateSampler() {
|
|
4526
|
+
let base;
|
|
4527
|
+
let lastFrames = 0;
|
|
4528
|
+
return {
|
|
4529
|
+
observe(done, at) {
|
|
4530
|
+
if (done === 0 || done < lastFrames) base = void 0;
|
|
4531
|
+
lastFrames = done;
|
|
4532
|
+
if (done === 0) return void 0;
|
|
4533
|
+
base ??= { at, frames: done };
|
|
4534
|
+
const sampleMs = at - base.at;
|
|
4535
|
+
const sampleFrames = done - base.frames;
|
|
4536
|
+
if (sampleMs < MIN_SAMPLE_MS || sampleFrames < MIN_SAMPLE_FRAMES) return void 0;
|
|
4537
|
+
return sampleMs / sampleFrames;
|
|
4538
|
+
}
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
function startRenderEtaGuard(options) {
|
|
4542
|
+
const { maxRenderMs, job, totalFrames, pipelineStartMs, onTooSlow, now = Date.now } = options;
|
|
4543
|
+
if (!maxRenderMs || maxRenderMs <= 0) return () => {
|
|
4544
|
+
};
|
|
4545
|
+
const sampler = createRateSampler();
|
|
4546
|
+
const stop = () => clearInterval(timer);
|
|
4547
|
+
const timer = setInterval(() => {
|
|
4548
|
+
const done = job.framesRendered ?? 0;
|
|
4549
|
+
if (done >= totalFrames) return stop();
|
|
4550
|
+
const at = now();
|
|
4551
|
+
const msPerFrame = sampler.observe(done, at);
|
|
4552
|
+
if (msPerFrame === void 0) return;
|
|
4553
|
+
const error = checkRenderEta("capture", {
|
|
4554
|
+
maxRenderMs,
|
|
4555
|
+
elapsedMs: at - pipelineStartMs,
|
|
4556
|
+
framesCompleted: done,
|
|
4557
|
+
totalFrames,
|
|
4558
|
+
msPerFrame
|
|
4559
|
+
});
|
|
4560
|
+
if (!error) return;
|
|
4561
|
+
stop();
|
|
4562
|
+
onTooSlow(error);
|
|
4563
|
+
}, options.pollMs ?? POLL_MS);
|
|
4564
|
+
timer.unref?.();
|
|
4565
|
+
return stop;
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4374
4568
|
// src/services/render/segmentManifest.ts
|
|
4375
4569
|
import { createHash as createHash2 } from "node:crypto";
|
|
4376
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as
|
|
4377
|
-
import { join as
|
|
4570
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4571
|
+
import { join as join11 } from "node:path";
|
|
4378
4572
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
4379
4573
|
var MANIFEST_FILENAME = "segments.json";
|
|
4380
4574
|
function computeSegmentPlanHash(input) {
|
|
@@ -4400,7 +4594,7 @@ function computeSegmentPlanHash(input) {
|
|
|
4400
4594
|
return createHash2("sha256").update(ordered).digest("hex").slice(0, 16);
|
|
4401
4595
|
}
|
|
4402
4596
|
function segmentDirFor(projectRendersDir, planHash) {
|
|
4403
|
-
return
|
|
4597
|
+
return join11(projectRendersDir, ".hf-segments", planHash);
|
|
4404
4598
|
}
|
|
4405
4599
|
function hasNumber(value, key) {
|
|
4406
4600
|
return key in value && typeof Reflect.get(value, key) === "number";
|
|
@@ -4418,7 +4612,7 @@ function isManifest(value) {
|
|
|
4418
4612
|
return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
|
|
4419
4613
|
}
|
|
4420
4614
|
function readSegmentManifest(segmentDir) {
|
|
4421
|
-
const path =
|
|
4615
|
+
const path = join11(segmentDir, MANIFEST_FILENAME);
|
|
4422
4616
|
if (!existsSync7(path)) return null;
|
|
4423
4617
|
try {
|
|
4424
4618
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -4429,9 +4623,9 @@ function readSegmentManifest(segmentDir) {
|
|
|
4429
4623
|
}
|
|
4430
4624
|
function writeSegmentManifest(segmentDir, manifest) {
|
|
4431
4625
|
mkdirSync4(segmentDir, { recursive: true });
|
|
4432
|
-
const tmp =
|
|
4626
|
+
const tmp = join11(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
|
|
4433
4627
|
writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
|
|
4434
|
-
renameWithRetry2(tmp,
|
|
4628
|
+
renameWithRetry2(tmp, join11(segmentDir, MANIFEST_FILENAME));
|
|
4435
4629
|
}
|
|
4436
4630
|
async function probeSegmentFrameCount(path, probe) {
|
|
4437
4631
|
try {
|
|
@@ -4449,7 +4643,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
4449
4643
|
if (manifest.planHash !== expectedHash) return ok;
|
|
4450
4644
|
for (const entry of manifest.completed) {
|
|
4451
4645
|
if (!existsSync7(entry.path)) continue;
|
|
4452
|
-
const size =
|
|
4646
|
+
const size = statSync4(entry.path).size;
|
|
4453
4647
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
4454
4648
|
const frames = await probeFrames(entry.path);
|
|
4455
4649
|
if (frames !== entry.endFrame - entry.startFrame) continue;
|
|
@@ -4543,7 +4737,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
|
|
|
4543
4737
|
}
|
|
4544
4738
|
|
|
4545
4739
|
// src/services/render/captureCost.ts
|
|
4546
|
-
import { join as
|
|
4740
|
+
import { join as join12 } from "node:path";
|
|
4547
4741
|
import { fpsToNumber as fpsToNumber5 } from "@hyperframes/core";
|
|
4548
4742
|
import {
|
|
4549
4743
|
calculateOptimalWorkers,
|
|
@@ -4788,7 +4982,7 @@ async function runCaptureCalibration(input) {
|
|
|
4788
4982
|
});
|
|
4789
4983
|
let calibration;
|
|
4790
4984
|
try {
|
|
4791
|
-
calibration = await runOneCalibration(
|
|
4985
|
+
calibration = await runOneCalibration(join12(workDir, "capture-calibration"), calibrationCfg);
|
|
4792
4986
|
} catch (error) {
|
|
4793
4987
|
const shouldFallback = !forceScreenshot && shouldFallbackToScreenshotAfterCalibrationError(error);
|
|
4794
4988
|
if (!shouldFallback) {
|
|
@@ -4821,7 +5015,7 @@ async function runCaptureCalibration(input) {
|
|
|
4821
5015
|
const screenshotCfg = createCaptureCalibrationConfig({ ...cfg, forceScreenshot: true });
|
|
4822
5016
|
try {
|
|
4823
5017
|
calibration = await runOneCalibration(
|
|
4824
|
-
|
|
5018
|
+
join12(workDir, "capture-calibration-screenshot"),
|
|
4825
5019
|
screenshotCfg
|
|
4826
5020
|
);
|
|
4827
5021
|
} catch (fallbackError) {
|
|
@@ -5253,14 +5447,14 @@ function countAuthoredTimedClips(html) {
|
|
|
5253
5447
|
}
|
|
5254
5448
|
|
|
5255
5449
|
// src/services/render/stages/compileStage.ts
|
|
5256
|
-
import { join as
|
|
5450
|
+
import { join as join15 } from "node:path";
|
|
5257
5451
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5258
5452
|
|
|
5259
5453
|
// src/services/htmlCompiler.ts
|
|
5260
5454
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5261
5455
|
import { createHash as createHash5 } from "node:crypto";
|
|
5262
5456
|
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
|
|
5263
|
-
import { join as
|
|
5457
|
+
import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
|
|
5264
5458
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
5265
5459
|
import {
|
|
5266
5460
|
COMPOSITION_SOURCE_URL,
|
|
@@ -5487,8 +5681,8 @@ import {
|
|
|
5487
5681
|
|
|
5488
5682
|
// src/services/animatedGifPrep.ts
|
|
5489
5683
|
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
|
|
5684
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
|
|
5685
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join13, resolve as resolve5 } from "node:path";
|
|
5492
5686
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
5493
5687
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
5494
5688
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -5537,7 +5731,7 @@ function resolveGifSourcePath(src, options) {
|
|
|
5537
5731
|
}
|
|
5538
5732
|
function isUsableFile(path) {
|
|
5539
5733
|
try {
|
|
5540
|
-
const stat =
|
|
5734
|
+
const stat = statSync5(path);
|
|
5541
5735
|
return stat.isFile() && stat.size > 0;
|
|
5542
5736
|
} catch {
|
|
5543
5737
|
return false;
|
|
@@ -5672,10 +5866,10 @@ async function ensurePreparedWebm(input) {
|
|
|
5672
5866
|
if (!isUsableFile(input.cachePath)) {
|
|
5673
5867
|
renameWithRetry3(tmpPath, input.cachePath);
|
|
5674
5868
|
} else {
|
|
5675
|
-
|
|
5869
|
+
rmSync7(tmpPath, { force: true });
|
|
5676
5870
|
}
|
|
5677
5871
|
} catch (error) {
|
|
5678
|
-
|
|
5872
|
+
rmSync7(tmpPath, { force: true });
|
|
5679
5873
|
throw error;
|
|
5680
5874
|
}
|
|
5681
5875
|
}
|
|
@@ -5731,7 +5925,7 @@ function replaceImageWithVideo(input) {
|
|
|
5731
5925
|
return video;
|
|
5732
5926
|
}
|
|
5733
5927
|
async function prepareAnimatedGifInputs(html, options) {
|
|
5734
|
-
const outputDir = options.outputDir ??
|
|
5928
|
+
const outputDir = options.outputDir ?? join13(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
5735
5929
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
5736
5930
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
5737
5931
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -5753,8 +5947,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
5753
5947
|
const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
|
|
5754
5948
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
5755
5949
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
5756
|
-
const cachePath =
|
|
5757
|
-
const outputPath =
|
|
5950
|
+
const cachePath = join13(cacheDir, filename);
|
|
5951
|
+
const outputPath = join13(outputDir, filename);
|
|
5758
5952
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
5759
5953
|
await ensurePreparedWebm({
|
|
5760
5954
|
sourcePath,
|
|
@@ -6168,7 +6362,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6168
6362
|
}
|
|
6169
6363
|
} else {
|
|
6170
6364
|
filePath = decodedUrlPath(src);
|
|
6171
|
-
if (!filePath.startsWith("/")) filePath =
|
|
6365
|
+
if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
|
|
6172
6366
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
6173
6367
|
}
|
|
6174
6368
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -6788,7 +6982,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
6788
6982
|
return downloadAndRewriteUrls(
|
|
6789
6983
|
urlSet,
|
|
6790
6984
|
html,
|
|
6791
|
-
|
|
6985
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6792
6986
|
"Remote media download failed",
|
|
6793
6987
|
"Localized remote media source(s)"
|
|
6794
6988
|
);
|
|
@@ -6803,7 +6997,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
6803
6997
|
return downloadAndRewriteUrls(
|
|
6804
6998
|
urlSet,
|
|
6805
6999
|
html,
|
|
6806
|
-
|
|
7000
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6807
7001
|
"Remote image download failed",
|
|
6808
7002
|
"Localized remote image source(s)"
|
|
6809
7003
|
);
|
|
@@ -6819,7 +7013,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
6819
7013
|
return downloadAndRewriteUrls(
|
|
6820
7014
|
urlSet,
|
|
6821
7015
|
html,
|
|
6822
|
-
|
|
7016
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6823
7017
|
"Remote background-image download failed",
|
|
6824
7018
|
"Localized remote background-image(s)",
|
|
6825
7019
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -6954,7 +7148,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
6954
7148
|
return downloadAndRewriteUrls(
|
|
6955
7149
|
urlSet,
|
|
6956
7150
|
processed,
|
|
6957
|
-
|
|
7151
|
+
join14(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
6958
7152
|
"Remote font download failed",
|
|
6959
7153
|
"Localized remote font face(s)",
|
|
6960
7154
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -7621,7 +7815,7 @@ async function runCompileStage(input) {
|
|
|
7621
7815
|
abortSignal
|
|
7622
7816
|
} = input;
|
|
7623
7817
|
const compileStart = Date.now();
|
|
7624
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
7818
|
+
const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
|
|
7625
7819
|
log,
|
|
7626
7820
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
7627
7821
|
allowSystemFontCapture,
|
|
@@ -7629,7 +7823,7 @@ async function runCompileStage(input) {
|
|
|
7629
7823
|
variables: input.variables,
|
|
7630
7824
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7631
7825
|
pageMotionBlur: input.pageMotionBlur,
|
|
7632
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
7826
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7633
7827
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7634
7828
|
});
|
|
7635
7829
|
assertNotAborted();
|
|
@@ -7692,7 +7886,7 @@ async function runCompileStage(input) {
|
|
|
7692
7886
|
};
|
|
7693
7887
|
await preflightCompositionAssetMediaTypes({
|
|
7694
7888
|
projectDir,
|
|
7695
|
-
compiledDir:
|
|
7889
|
+
compiledDir: join15(workDir, "compiled"),
|
|
7696
7890
|
composition,
|
|
7697
7891
|
signal: abortSignal
|
|
7698
7892
|
});
|
|
@@ -7737,7 +7931,7 @@ async function runCompileStage(input) {
|
|
|
7737
7931
|
}
|
|
7738
7932
|
|
|
7739
7933
|
// src/services/render/stages/probeStage.ts
|
|
7740
|
-
import { join as
|
|
7934
|
+
import { join as join16 } from "node:path";
|
|
7741
7935
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
7742
7936
|
import {
|
|
7743
7937
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -7929,7 +8123,7 @@ async function runProbeStage(input) {
|
|
|
7929
8123
|
});
|
|
7930
8124
|
fileServer = await createFileServer({
|
|
7931
8125
|
projectDir,
|
|
7932
|
-
compiledDir:
|
|
8126
|
+
compiledDir: join16(workDir, "compiled"),
|
|
7933
8127
|
port: 0,
|
|
7934
8128
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
7935
8129
|
fps: job.config.fps
|
|
@@ -7957,7 +8151,7 @@ async function runProbeStage(input) {
|
|
|
7957
8151
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
7958
8152
|
probeSession = await createCaptureSession4(
|
|
7959
8153
|
fileServer.url,
|
|
7960
|
-
|
|
8154
|
+
join16(workDir, "probe"),
|
|
7961
8155
|
captureOpts,
|
|
7962
8156
|
null,
|
|
7963
8157
|
probeCfg
|
|
@@ -8042,7 +8236,7 @@ async function runProbeStage(input) {
|
|
|
8042
8236
|
});
|
|
8043
8237
|
probeSession = await createCaptureSession4(
|
|
8044
8238
|
fileServer.url,
|
|
8045
|
-
|
|
8239
|
+
join16(workDir, "probe-screenshot"),
|
|
8046
8240
|
captureOpts,
|
|
8047
8241
|
null,
|
|
8048
8242
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -8078,7 +8272,7 @@ async function runProbeStage(input) {
|
|
|
8078
8272
|
compiled,
|
|
8079
8273
|
resolutions,
|
|
8080
8274
|
projectDir,
|
|
8081
|
-
|
|
8275
|
+
join16(workDir, "downloads")
|
|
8082
8276
|
);
|
|
8083
8277
|
assertNotAborted();
|
|
8084
8278
|
composition.videos = compiled.videos;
|
|
@@ -8269,7 +8463,7 @@ async function runProbeStage(input) {
|
|
|
8269
8463
|
}
|
|
8270
8464
|
await preflightCompositionAssetMediaTypes({
|
|
8271
8465
|
projectDir,
|
|
8272
|
-
compiledDir:
|
|
8466
|
+
compiledDir: join16(workDir, "compiled"),
|
|
8273
8467
|
composition,
|
|
8274
8468
|
signal: abortSignal
|
|
8275
8469
|
});
|
|
@@ -8434,7 +8628,7 @@ function validateDistributedDuration(input) {
|
|
|
8434
8628
|
|
|
8435
8629
|
// src/services/render/stages/extractVideosStage.ts
|
|
8436
8630
|
import { existsSync as existsSync11 } from "node:fs";
|
|
8437
|
-
import { join as
|
|
8631
|
+
import { join as join17 } from "node:path";
|
|
8438
8632
|
import {
|
|
8439
8633
|
classifyVideoExtractionError,
|
|
8440
8634
|
createFrameLookupTable,
|
|
@@ -8754,14 +8948,14 @@ async function runExtractVideosStage(input) {
|
|
|
8754
8948
|
// because short boundary counts can differ by one frame.
|
|
8755
8949
|
{
|
|
8756
8950
|
fps: job.config.fps,
|
|
8757
|
-
outputDir:
|
|
8951
|
+
outputDir: join17(compiledDir, "__hyperframes_video_frames"),
|
|
8758
8952
|
format: job.config.videoFrameFormat ?? "auto",
|
|
8759
8953
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
8760
8954
|
timelineEnd: composition.duration,
|
|
8761
8955
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
8762
8956
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
8763
8957
|
deferRangeExtraction,
|
|
8764
|
-
contentKeyedDirs: [
|
|
8958
|
+
contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
8765
8959
|
urlByAbsentLocalPath
|
|
8766
8960
|
},
|
|
8767
8961
|
abortSignal,
|
|
@@ -8828,8 +9022,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8828
9022
|
}
|
|
8829
9023
|
|
|
8830
9024
|
// src/services/render/stages/audioStage.ts
|
|
8831
|
-
import { join as
|
|
8832
|
-
import { rmSync as
|
|
9025
|
+
import { join as join19 } from "node:path";
|
|
9026
|
+
import { rmSync as rmSync9 } from "node:fs";
|
|
8833
9027
|
import {
|
|
8834
9028
|
MIXED_AUDIO_FILENAME,
|
|
8835
9029
|
audioFailureDiagnostics,
|
|
@@ -8838,8 +9032,8 @@ import {
|
|
|
8838
9032
|
|
|
8839
9033
|
// src/services/render/audioPadTrim.ts
|
|
8840
9034
|
import { spawn } from "node:child_process";
|
|
8841
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
8842
|
-
import { dirname as dirname7, join as
|
|
9035
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
|
|
9036
|
+
import { dirname as dirname7, join as join18 } from "node:path";
|
|
8843
9037
|
import {
|
|
8844
9038
|
DEFAULT_CONFIG,
|
|
8845
9039
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -9042,7 +9236,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
9042
9236
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
9043
9237
|
};
|
|
9044
9238
|
} finally {
|
|
9045
|
-
for (const path of plan2.cleanupPaths)
|
|
9239
|
+
for (const path of plan2.cleanupPaths) rmSync8(path, { force: true });
|
|
9046
9240
|
}
|
|
9047
9241
|
let loweredDb = 0;
|
|
9048
9242
|
if (probeTruePeak) {
|
|
@@ -9088,8 +9282,8 @@ async function constrainAacTruePeak(input) {
|
|
|
9088
9282
|
async function enforceAacTruePeak(input) {
|
|
9089
9283
|
let scratchDir;
|
|
9090
9284
|
try {
|
|
9091
|
-
scratchDir = mkdtempSync3(
|
|
9092
|
-
const correctedPath =
|
|
9285
|
+
scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
|
|
9286
|
+
const correctedPath = join18(scratchDir, "audio.m4a");
|
|
9093
9287
|
const measurements = [];
|
|
9094
9288
|
let attenuationDb = 0;
|
|
9095
9289
|
let measuredPath = input.audioPath;
|
|
@@ -9140,7 +9334,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9140
9334
|
)}`
|
|
9141
9335
|
};
|
|
9142
9336
|
} finally {
|
|
9143
|
-
if (scratchDir)
|
|
9337
|
+
if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
|
|
9144
9338
|
}
|
|
9145
9339
|
}
|
|
9146
9340
|
function failResult(outputPath, target, source, error) {
|
|
@@ -9287,7 +9481,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
9287
9481
|
}
|
|
9288
9482
|
async function runAudioStage(input) {
|
|
9289
9483
|
const stageStart = Date.now();
|
|
9290
|
-
const pcmSourcePath =
|
|
9484
|
+
const pcmSourcePath = join19(input.workDir, "audio-source.wav");
|
|
9291
9485
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
9292
9486
|
try {
|
|
9293
9487
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -9307,7 +9501,7 @@ async function runAudioStage(input) {
|
|
|
9307
9501
|
}
|
|
9308
9502
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9309
9503
|
} finally {
|
|
9310
|
-
|
|
9504
|
+
rmSync9(pcmSourcePath, { force: true });
|
|
9311
9505
|
}
|
|
9312
9506
|
}
|
|
9313
9507
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -9325,7 +9519,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9325
9519
|
assertNotAborted,
|
|
9326
9520
|
log
|
|
9327
9521
|
} = input;
|
|
9328
|
-
const audioOutputPath =
|
|
9522
|
+
const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
|
|
9329
9523
|
let hasAudio = false;
|
|
9330
9524
|
let audioError;
|
|
9331
9525
|
let audioFailures;
|
|
@@ -9336,7 +9530,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9336
9530
|
audioResult = await processCompositionAudio(
|
|
9337
9531
|
audios,
|
|
9338
9532
|
projectDir,
|
|
9339
|
-
|
|
9533
|
+
join19(workDir, "audio-work"),
|
|
9340
9534
|
audioOutputPath,
|
|
9341
9535
|
duration,
|
|
9342
9536
|
abortSignal,
|
|
@@ -9405,7 +9599,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9405
9599
|
|
|
9406
9600
|
// src/services/render/stages/captureStage.ts
|
|
9407
9601
|
import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
9408
|
-
import { join as
|
|
9602
|
+
import { join as join20 } from "node:path";
|
|
9409
9603
|
import {
|
|
9410
9604
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
9411
9605
|
frameFileExtension,
|
|
@@ -9478,13 +9672,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
9478
9672
|
if (!existsSync12(workDir)) return total;
|
|
9479
9673
|
for (const attempt of readdirSync3(workDir)) {
|
|
9480
9674
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
9481
|
-
total += sampleDirectoryBytes(
|
|
9675
|
+
total += sampleDirectoryBytes(join20(workDir, attempt));
|
|
9482
9676
|
continue;
|
|
9483
9677
|
}
|
|
9484
9678
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
9485
|
-
const attemptDir =
|
|
9679
|
+
const attemptDir = join20(workDir, attempt);
|
|
9486
9680
|
for (const name of readdirSync3(attemptDir)) {
|
|
9487
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
9681
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
|
|
9488
9682
|
}
|
|
9489
9683
|
}
|
|
9490
9684
|
return total;
|
|
@@ -9771,7 +9965,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
9771
9965
|
|
|
9772
9966
|
// src/services/render/stages/captureHdrStage.ts
|
|
9773
9967
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
|
|
9774
|
-
import { join as
|
|
9968
|
+
import { join as join24 } from "node:path";
|
|
9775
9969
|
import {
|
|
9776
9970
|
closeCaptureSession as closeCaptureSession7,
|
|
9777
9971
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -9843,7 +10037,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
9843
10037
|
}
|
|
9844
10038
|
|
|
9845
10039
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
9846
|
-
import { join as
|
|
10040
|
+
import { join as join21 } from "node:path";
|
|
9847
10041
|
import {
|
|
9848
10042
|
TRANSITIONS,
|
|
9849
10043
|
crossfade
|
|
@@ -9968,7 +10162,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
9968
10162
|
);
|
|
9969
10163
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
9970
10164
|
writeFileExclusiveSync(
|
|
9971
|
-
|
|
10165
|
+
join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
9972
10166
|
normalCanvas
|
|
9973
10167
|
);
|
|
9974
10168
|
}
|
|
@@ -10000,7 +10194,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10000
10194
|
}
|
|
10001
10195
|
|
|
10002
10196
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
10003
|
-
import { join as
|
|
10197
|
+
import { join as join23 } from "node:path";
|
|
10004
10198
|
import {
|
|
10005
10199
|
TRANSITIONS as TRANSITIONS2,
|
|
10006
10200
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -10014,7 +10208,7 @@ import {
|
|
|
10014
10208
|
// src/services/shaderTransitionWorkerPool.ts
|
|
10015
10209
|
import { Worker } from "node:worker_threads";
|
|
10016
10210
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
10017
|
-
import { dirname as dirname8, join as
|
|
10211
|
+
import { dirname as dirname8, join as join22 } from "node:path";
|
|
10018
10212
|
import { createRequire } from "node:module";
|
|
10019
10213
|
import { existsSync as existsSync13 } from "node:fs";
|
|
10020
10214
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -10028,9 +10222,9 @@ function resolveWorkerEntry(explicit) {
|
|
|
10028
10222
|
return { path: override, isTs };
|
|
10029
10223
|
}
|
|
10030
10224
|
const moduleDir = dirname8(fileURLToPath3(import.meta.url));
|
|
10031
|
-
const jsPath =
|
|
10225
|
+
const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
|
|
10032
10226
|
if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
|
|
10033
|
-
const tsPath =
|
|
10227
|
+
const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
|
|
10034
10228
|
return { path: tsPath, isTs: true };
|
|
10035
10229
|
}
|
|
10036
10230
|
function buildExecArgv(entryIsTs) {
|
|
@@ -10398,7 +10592,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
10398
10592
|
);
|
|
10399
10593
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10400
10594
|
writeFileExclusiveSync(
|
|
10401
|
-
|
|
10595
|
+
join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10402
10596
|
canvas
|
|
10403
10597
|
);
|
|
10404
10598
|
}
|
|
@@ -10567,7 +10761,8 @@ async function runCaptureHdrStage(input) {
|
|
|
10567
10761
|
width,
|
|
10568
10762
|
height,
|
|
10569
10763
|
abortSignal,
|
|
10570
|
-
hdrDiagnostics
|
|
10764
|
+
hdrDiagnostics,
|
|
10765
|
+
maxResidentWindows: Math.max(1, workerCount) + 1
|
|
10571
10766
|
});
|
|
10572
10767
|
releaseHdrExtractionReservation = extracted.releaseReservation;
|
|
10573
10768
|
for (const [id, source] of extracted.sources) hdrVideoFrameSources.set(id, source);
|
|
@@ -10585,7 +10780,7 @@ async function runCaptureHdrStage(input) {
|
|
|
10585
10780
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
10586
10781
|
}
|
|
10587
10782
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
10588
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
10783
|
+
const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
|
|
10589
10784
|
if (debugDumpDir && !existsSync14(debugDumpDir)) {
|
|
10590
10785
|
mkdirSync7(debugDumpDir, { recursive: true });
|
|
10591
10786
|
}
|
|
@@ -10745,12 +10940,12 @@ import {
|
|
|
10745
10940
|
mkdirSync as mkdirSync8,
|
|
10746
10941
|
readdirSync as readdirSync4,
|
|
10747
10942
|
readFileSync as readFileSync7,
|
|
10748
|
-
rmSync as
|
|
10749
|
-
statSync as
|
|
10943
|
+
rmSync as rmSync10,
|
|
10944
|
+
statSync as statSync6,
|
|
10750
10945
|
writeFileSync as writeFileSync4
|
|
10751
10946
|
} from "node:fs";
|
|
10752
10947
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
10753
|
-
import { dirname as dirname9, join as
|
|
10948
|
+
import { dirname as dirname9, join as join25 } from "node:path";
|
|
10754
10949
|
import {
|
|
10755
10950
|
encodeFramesChunkedConcat,
|
|
10756
10951
|
encodeFramesFromDir,
|
|
@@ -10875,7 +11070,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
10875
11070
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
10876
11071
|
if (failure) return failure;
|
|
10877
11072
|
}
|
|
10878
|
-
const fileSize = existsSync15(outputPath) ?
|
|
11073
|
+
const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
|
|
10879
11074
|
return {
|
|
10880
11075
|
success: true,
|
|
10881
11076
|
outputPath,
|
|
@@ -10884,7 +11079,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
10884
11079
|
fileSize
|
|
10885
11080
|
};
|
|
10886
11081
|
} finally {
|
|
10887
|
-
|
|
11082
|
+
rmSync10(input.palettePath, { force: true });
|
|
10888
11083
|
}
|
|
10889
11084
|
}
|
|
10890
11085
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -10959,7 +11154,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
10959
11154
|
await new Promise((resolve12) => wake = resolve12);
|
|
10960
11155
|
}
|
|
10961
11156
|
if (signal.aborted) return;
|
|
10962
|
-
const mergedPath =
|
|
11157
|
+
const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
10963
11158
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
10964
11159
|
landed.delete(next);
|
|
10965
11160
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -11042,11 +11237,11 @@ async function runEncodeStage(input) {
|
|
|
11042
11237
|
);
|
|
11043
11238
|
}
|
|
11044
11239
|
captured.forEach((name, i) => {
|
|
11045
|
-
const dst =
|
|
11046
|
-
copyFileSync3(
|
|
11240
|
+
const dst = join25(outputPath, formatExportFrameName(i, "png"));
|
|
11241
|
+
copyFileSync3(join25(framesDir, name), dst);
|
|
11047
11242
|
});
|
|
11048
11243
|
if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
|
|
11049
|
-
copyFileSync3(audioOutputPath,
|
|
11244
|
+
copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
|
|
11050
11245
|
log.info(
|
|
11051
11246
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
11052
11247
|
);
|
|
@@ -11066,7 +11261,7 @@ async function runEncodeStage(input) {
|
|
|
11066
11261
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
11067
11262
|
fps: job.config.fps,
|
|
11068
11263
|
loop,
|
|
11069
|
-
palettePath:
|
|
11264
|
+
palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
|
|
11070
11265
|
preserveAlpha: needsAlpha,
|
|
11071
11266
|
signal: abortSignal,
|
|
11072
11267
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -11279,9 +11474,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
11279
11474
|
continue;
|
|
11280
11475
|
}
|
|
11281
11476
|
for (const name of entries) {
|
|
11282
|
-
const full =
|
|
11477
|
+
const full = join26(current, name);
|
|
11283
11478
|
try {
|
|
11284
|
-
const st =
|
|
11479
|
+
const st = statSync7(full);
|
|
11285
11480
|
if (st.isDirectory()) {
|
|
11286
11481
|
stack.push(full);
|
|
11287
11482
|
} else if (st.isFile()) {
|
|
@@ -11428,8 +11623,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11428
11623
|
const ranges = [];
|
|
11429
11624
|
let rangeStart = null;
|
|
11430
11625
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11431
|
-
const framePath =
|
|
11432
|
-
const missing = !existsSync16(framePath) ||
|
|
11626
|
+
const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11627
|
+
const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
|
|
11433
11628
|
if (missing && rangeStart === null) {
|
|
11434
11629
|
rangeStart = frameIndex;
|
|
11435
11630
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -11451,7 +11646,7 @@ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, ran
|
|
|
11451
11646
|
workerId,
|
|
11452
11647
|
startFrame: rangeStart + range.startFrame,
|
|
11453
11648
|
endFrame: rangeStart + range.endFrame,
|
|
11454
|
-
outputDir:
|
|
11649
|
+
outputDir: join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
|
|
11455
11650
|
outputFrameOffset: rangeStart
|
|
11456
11651
|
}));
|
|
11457
11652
|
batches.push(batch);
|
|
@@ -11479,8 +11674,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11479
11674
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11480
11675
|
}
|
|
11481
11676
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11482
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
11483
|
-
return
|
|
11677
|
+
if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
|
|
11678
|
+
return join26(dirname10(outputPath), `work-${jobId}-`);
|
|
11484
11679
|
}
|
|
11485
11680
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
11486
11681
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -11512,7 +11707,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
11512
11707
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
11513
11708
|
let captured = 0;
|
|
11514
11709
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11515
|
-
const framePath =
|
|
11710
|
+
const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11516
11711
|
if (existsSync16(framePath)) captured++;
|
|
11517
11712
|
}
|
|
11518
11713
|
return captured;
|
|
@@ -11538,7 +11733,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11538
11733
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
11539
11734
|
});
|
|
11540
11735
|
pendingTransientRetry = false;
|
|
11541
|
-
const attemptWorkDir =
|
|
11736
|
+
const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
|
|
11542
11737
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
11543
11738
|
missingRanges,
|
|
11544
11739
|
currentWorkers,
|
|
@@ -11995,7 +12190,7 @@ function resolveDeShortBand(args) {
|
|
|
11995
12190
|
return args.bandOpen ? "applied" : "skipped_elements";
|
|
11996
12191
|
}
|
|
11997
12192
|
function shouldPreferSingleWorkerDrawElement(args) {
|
|
11998
|
-
return args.workerCount > 1 &&
|
|
12193
|
+
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;
|
|
11999
12194
|
}
|
|
12000
12195
|
function isProbeDeGated(session) {
|
|
12001
12196
|
return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
|
|
@@ -12131,17 +12326,17 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
12131
12326
|
}
|
|
12132
12327
|
function resolveRenderDebugDir() {
|
|
12133
12328
|
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
|
|
12134
|
-
return
|
|
12329
|
+
return join26(producerRoot, ".debug");
|
|
12135
12330
|
}
|
|
12136
12331
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
12137
12332
|
validateHlsRenderConfig(job.config);
|
|
12138
12333
|
const debugDir = resolveRenderDebugDir();
|
|
12139
12334
|
const outputDir = dirname10(outputPath);
|
|
12140
12335
|
if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
|
|
12141
|
-
const workDir = job.config.debug ?
|
|
12336
|
+
const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
12142
12337
|
const pipelineStart = Date.now();
|
|
12143
12338
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
12144
|
-
const logPath = job.config.debug ?
|
|
12339
|
+
const logPath = job.config.debug ? join26(workDir, "render.log") : null;
|
|
12145
12340
|
const execution = new RenderExecutionContext({
|
|
12146
12341
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
12147
12342
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -12155,7 +12350,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
12155
12350
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
12156
12351
|
return;
|
|
12157
12352
|
}
|
|
12158
|
-
|
|
12353
|
+
rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
12159
12354
|
});
|
|
12160
12355
|
try {
|
|
12161
12356
|
await executeRenderPipeline({
|
|
@@ -12186,7 +12381,9 @@ async function executeRenderPipeline(input) {
|
|
|
12186
12381
|
const log = execution.logger;
|
|
12187
12382
|
const eventPublisher = execution.events;
|
|
12188
12383
|
const onProgress = execution.onProgress;
|
|
12189
|
-
const
|
|
12384
|
+
const tooSlow = new AbortController();
|
|
12385
|
+
let tooSlowError;
|
|
12386
|
+
const executionSignal = job.config.maxRenderMs ? AbortSignal.any([tooSlow.signal, ...execution.signal ? [execution.signal] : []]) : execution.signal;
|
|
12190
12387
|
let fileServer = null;
|
|
12191
12388
|
let probeSession = null;
|
|
12192
12389
|
let lastBrowserConsole = [];
|
|
@@ -12199,7 +12396,7 @@ async function executeRenderPipeline(input) {
|
|
|
12199
12396
|
imageDecodeFailures: 0
|
|
12200
12397
|
};
|
|
12201
12398
|
let hdrPerf;
|
|
12202
|
-
const perfOutputPath =
|
|
12399
|
+
const perfOutputPath = join26(workDir, "perf-summary.json");
|
|
12203
12400
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
12204
12401
|
const observability = new RenderObservabilityRecorder({
|
|
12205
12402
|
pipelineStartMs: pipelineStart,
|
|
@@ -12301,15 +12498,15 @@ async function executeRenderPipeline(input) {
|
|
|
12301
12498
|
requestedWorkers: job.config.workers ?? "auto"
|
|
12302
12499
|
});
|
|
12303
12500
|
const entryFile = job.config.entryFile || "index.html";
|
|
12304
|
-
let htmlPath =
|
|
12501
|
+
let htmlPath = join26(projectDir, entryFile);
|
|
12305
12502
|
if (!existsSync16(htmlPath)) {
|
|
12306
12503
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
12307
12504
|
}
|
|
12308
12505
|
assertNotAborted();
|
|
12309
12506
|
const rawEntry = readFileSync8(htmlPath, "utf-8");
|
|
12310
12507
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
12311
|
-
const wrapperPath =
|
|
12312
|
-
const projectIndexPath =
|
|
12508
|
+
const wrapperPath = join26(workDir, "standalone-entry.html");
|
|
12509
|
+
const projectIndexPath = join26(projectDir, "index.html");
|
|
12313
12510
|
if (!existsSync16(projectIndexPath)) {
|
|
12314
12511
|
throw new Error(
|
|
12315
12512
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -12472,7 +12669,7 @@ async function executeRenderPipeline(input) {
|
|
|
12472
12669
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
12473
12670
|
code: "extract_video"
|
|
12474
12671
|
});
|
|
12475
|
-
const compiledDir =
|
|
12672
|
+
const compiledDir = join26(workDir, "compiled");
|
|
12476
12673
|
const extractResult = await observeRenderStage(
|
|
12477
12674
|
observability,
|
|
12478
12675
|
"video_extract",
|
|
@@ -12606,7 +12803,7 @@ async function executeRenderPipeline(input) {
|
|
|
12606
12803
|
try {
|
|
12607
12804
|
fileServer = await createFileServer({
|
|
12608
12805
|
projectDir,
|
|
12609
|
-
compiledDir:
|
|
12806
|
+
compiledDir: join26(workDir, "compiled"),
|
|
12610
12807
|
port: 0,
|
|
12611
12808
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
12612
12809
|
fps: job.config.fps
|
|
@@ -12624,7 +12821,7 @@ async function executeRenderPipeline(input) {
|
|
|
12624
12821
|
if (!activeFileServer) {
|
|
12625
12822
|
throw new Error("File server failed to initialize before frame capture");
|
|
12626
12823
|
}
|
|
12627
|
-
const framesDir =
|
|
12824
|
+
const framesDir = join26(workDir, "captured-frames");
|
|
12628
12825
|
if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
|
|
12629
12826
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
12630
12827
|
code: "start_browsers"
|
|
@@ -12728,7 +12925,6 @@ async function executeRenderPipeline(input) {
|
|
|
12728
12925
|
const WOULD_RESOLVE_MULTI_WORKER = 2;
|
|
12729
12926
|
const deInversionArgs = {
|
|
12730
12927
|
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
|
12731
|
-
requestedWorkers: job.config.workers,
|
|
12732
12928
|
useDrawElement: cfg.useDrawElement,
|
|
12733
12929
|
deCompileGate,
|
|
12734
12930
|
forceScreenshot: captureForceScreenshot,
|
|
@@ -12934,7 +13130,7 @@ async function executeRenderPipeline(input) {
|
|
|
12934
13130
|
} else if (deInversionEligible && workerCount > 1) {
|
|
12935
13131
|
deWorkerInversion = "inverted";
|
|
12936
13132
|
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
|
|
13133
|
+
`[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
13134
|
);
|
|
12939
13135
|
workerCount = 1;
|
|
12940
13136
|
}
|
|
@@ -12978,6 +13174,27 @@ async function executeRenderPipeline(input) {
|
|
|
12978
13174
|
deWorkerInversion: deWorkerInversion ?? "none",
|
|
12979
13175
|
deParallelRouter: deParallelRouter ?? "none"
|
|
12980
13176
|
});
|
|
13177
|
+
const calibratedTooSlow = checkRenderEta("capture_calibration", {
|
|
13178
|
+
maxRenderMs: job.config.maxRenderMs,
|
|
13179
|
+
elapsedMs: Date.now() - pipelineStart,
|
|
13180
|
+
framesCompleted: 0,
|
|
13181
|
+
totalFrames,
|
|
13182
|
+
msPerFrame: (captureCalibration?.estimate.p95Ms ?? 0) / workerCount
|
|
13183
|
+
});
|
|
13184
|
+
if (calibratedTooSlow) throw calibratedTooSlow;
|
|
13185
|
+
execution.defer(
|
|
13186
|
+
"stop render ETA guard",
|
|
13187
|
+
startRenderEtaGuard({
|
|
13188
|
+
maxRenderMs: job.config.maxRenderMs,
|
|
13189
|
+
job,
|
|
13190
|
+
totalFrames,
|
|
13191
|
+
pipelineStartMs: pipelineStart,
|
|
13192
|
+
onTooSlow: (error) => {
|
|
13193
|
+
tooSlowError = error;
|
|
13194
|
+
tooSlow.abort(error);
|
|
13195
|
+
}
|
|
13196
|
+
})
|
|
13197
|
+
);
|
|
12981
13198
|
let useStreamingEncode = shouldUseStreamingEncode(
|
|
12982
13199
|
cfg,
|
|
12983
13200
|
outputFormat,
|
|
@@ -13071,7 +13288,7 @@ async function executeRenderPipeline(input) {
|
|
|
13071
13288
|
hls: ".mp4"
|
|
13072
13289
|
};
|
|
13073
13290
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
13074
|
-
const videoOnlyPath =
|
|
13291
|
+
const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
|
|
13075
13292
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
13076
13293
|
if (usePageSideCompositingForTransitions) {
|
|
13077
13294
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -13317,7 +13534,7 @@ async function executeRenderPipeline(input) {
|
|
|
13317
13534
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
13318
13535
|
captureQuality: captureOptions.quality
|
|
13319
13536
|
});
|
|
13320
|
-
const segmentDir = segmentDirFor(
|
|
13537
|
+
const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
|
|
13321
13538
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
13322
13539
|
if (job.config.resumeSegments === true) {
|
|
13323
13540
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -13332,10 +13549,10 @@ async function executeRenderPipeline(input) {
|
|
|
13332
13549
|
});
|
|
13333
13550
|
}
|
|
13334
13551
|
if (completedSegments.size === 0) {
|
|
13335
|
-
|
|
13552
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13336
13553
|
}
|
|
13337
13554
|
} else {
|
|
13338
|
-
|
|
13555
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13339
13556
|
}
|
|
13340
13557
|
const segmentManifest = readSegmentManifest(segmentDir) ?? {
|
|
13341
13558
|
version: 1,
|
|
@@ -13416,7 +13633,7 @@ async function executeRenderPipeline(input) {
|
|
|
13416
13633
|
);
|
|
13417
13634
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
13418
13635
|
if (job.config.keepSegments !== true) {
|
|
13419
|
-
|
|
13636
|
+
rmSync11(segmentDir, { recursive: true, force: true });
|
|
13420
13637
|
}
|
|
13421
13638
|
} else {
|
|
13422
13639
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
@@ -13685,7 +13902,7 @@ async function executeRenderPipeline(input) {
|
|
|
13685
13902
|
"capture_disk",
|
|
13686
13903
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
13687
13904
|
);
|
|
13688
|
-
|
|
13905
|
+
rmSync11(framesDir, { recursive: true, force: true });
|
|
13689
13906
|
mkdirSync9(framesDir, { recursive: true });
|
|
13690
13907
|
resetCaptureAttemptProgress(job);
|
|
13691
13908
|
dedupPerfs.length = 0;
|
|
@@ -13872,7 +14089,7 @@ async function executeRenderPipeline(input) {
|
|
|
13872
14089
|
}
|
|
13873
14090
|
if (job.config.debug) {
|
|
13874
14091
|
if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
|
|
13875
|
-
const debugOutput =
|
|
14092
|
+
const debugOutput = join26(workDir, `output${videoExt}`);
|
|
13876
14093
|
copyFileSync4(stagedOutputPath, debugOutput);
|
|
13877
14094
|
}
|
|
13878
14095
|
}
|
|
@@ -13883,8 +14100,9 @@ async function executeRenderPipeline(input) {
|
|
|
13883
14100
|
job.outputPath = outputPath;
|
|
13884
14101
|
updateJobStatus(job, "complete", "Render complete", 100, onProgress);
|
|
13885
14102
|
await eventPublisher.flush();
|
|
13886
|
-
} catch (
|
|
13887
|
-
|
|
14103
|
+
} catch (caught) {
|
|
14104
|
+
const error = tooSlowError ?? caught;
|
|
14105
|
+
if (!tooSlowError && (error instanceof RenderCancelledError || executionSignal?.aborted)) {
|
|
13888
14106
|
job.error = error instanceof Error ? error.message : "render_cancelled";
|
|
13889
14107
|
updateJobStatus(job, "cancelled", "Render cancelled", job.progress, onProgress);
|
|
13890
14108
|
await eventPublisher.flush();
|
|
@@ -14001,10 +14219,10 @@ import {
|
|
|
14001
14219
|
existsSync as existsSync21,
|
|
14002
14220
|
linkSync,
|
|
14003
14221
|
mkdirSync as mkdirSync13,
|
|
14004
|
-
rmSync as
|
|
14222
|
+
rmSync as rmSync13,
|
|
14005
14223
|
writeFileSync as writeFileSync8
|
|
14006
14224
|
} from "node:fs";
|
|
14007
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
14225
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join34, relative as relative5, sep as sep2 } from "node:path";
|
|
14008
14226
|
import {
|
|
14009
14227
|
fpsToNumber as fpsToNumber10,
|
|
14010
14228
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -14019,7 +14237,7 @@ import {
|
|
|
14019
14237
|
} from "@hyperframes/engine";
|
|
14020
14238
|
|
|
14021
14239
|
// src/services/distributed/planFrameCost.ts
|
|
14022
|
-
import { join as
|
|
14240
|
+
import { join as join29 } from "node:path";
|
|
14023
14241
|
import {
|
|
14024
14242
|
closeCaptureSession as closeCaptureSession9,
|
|
14025
14243
|
createCaptureSession as createCaptureSession9,
|
|
@@ -14029,7 +14247,7 @@ import {
|
|
|
14029
14247
|
|
|
14030
14248
|
// src/services/distributed/chunkFrames.ts
|
|
14031
14249
|
import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
|
|
14032
|
-
import { extname as extname4, join as
|
|
14250
|
+
import { extname as extname4, join as join28 } from "node:path";
|
|
14033
14251
|
import {
|
|
14034
14252
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
14035
14253
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -14039,7 +14257,7 @@ import {
|
|
|
14039
14257
|
|
|
14040
14258
|
// src/services/distributed/shared.ts
|
|
14041
14259
|
import { execFile as execFileCallback } from "node:child_process";
|
|
14042
|
-
import { dirname as dirname11, join as
|
|
14260
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
14043
14261
|
import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
|
|
14044
14262
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
14045
14263
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -14055,7 +14273,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
14055
14273
|
}
|
|
14056
14274
|
function resolvePlanAudioPath(planDir) {
|
|
14057
14275
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
14058
|
-
const candidate =
|
|
14276
|
+
const candidate = join27(planDir, name);
|
|
14059
14277
|
if (existsSync17(candidate)) return candidate;
|
|
14060
14278
|
}
|
|
14061
14279
|
return null;
|
|
@@ -14315,7 +14533,7 @@ function readProducerVersion() {
|
|
|
14315
14533
|
const startDir = dirname11(fileURLToPath5(import.meta.url));
|
|
14316
14534
|
let current = startDir;
|
|
14317
14535
|
for (let i = 0; i < 10; i++) {
|
|
14318
|
-
const candidate =
|
|
14536
|
+
const candidate = join27(current, "package.json");
|
|
14319
14537
|
if (existsSync17(candidate)) {
|
|
14320
14538
|
try {
|
|
14321
14539
|
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
@@ -14369,7 +14587,7 @@ function frameNumberFromFileName(name) {
|
|
|
14369
14587
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
14370
14588
|
const result = [];
|
|
14371
14589
|
for (const v of videos) {
|
|
14372
|
-
const outputDir =
|
|
14590
|
+
const outputDir = join28(planDir, "video-frames", v.videoId);
|
|
14373
14591
|
if (!existsSync18(outputDir)) {
|
|
14374
14592
|
throw new Error(
|
|
14375
14593
|
`[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 +14606,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
14388
14606
|
if (!frameName) continue;
|
|
14389
14607
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
14390
14608
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
14391
|
-
framePaths.set(frameIndex,
|
|
14609
|
+
framePaths.set(frameIndex, join28(outputDir, frameName));
|
|
14392
14610
|
}
|
|
14393
14611
|
result.push({
|
|
14394
14612
|
videoId: v.videoId,
|
|
@@ -14478,14 +14696,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14478
14696
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
14479
14697
|
return Promise.all(
|
|
14480
14698
|
deferred.map(async (video) => {
|
|
14481
|
-
const outputDir =
|
|
14699
|
+
const outputDir = join28(input.outputRoot, video.videoId);
|
|
14482
14700
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
14483
14701
|
if (indexes.length === 0) {
|
|
14484
14702
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
14485
14703
|
}
|
|
14486
14704
|
const range = video.deferredRange;
|
|
14487
14705
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
14488
|
-
const sourcePath = remoteUrl ??
|
|
14706
|
+
const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
|
|
14489
14707
|
if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
|
|
14490
14708
|
throw new RenderChunkValidationError(
|
|
14491
14709
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -14534,12 +14752,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14534
14752
|
planVideos,
|
|
14535
14753
|
frames: sampled,
|
|
14536
14754
|
fps: fpsRatio,
|
|
14537
|
-
outputRoot:
|
|
14755
|
+
outputRoot: join29(workDir, "frame-cost-video-frames"),
|
|
14538
14756
|
cfg: input.cfg
|
|
14539
14757
|
})
|
|
14540
14758
|
];
|
|
14541
14759
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
14542
|
-
const compiledDir =
|
|
14760
|
+
const compiledDir = join29(planDir, "compiled");
|
|
14543
14761
|
const fileServer = await createFileServer({
|
|
14544
14762
|
projectDir: compiledDir,
|
|
14545
14763
|
compiledDir,
|
|
@@ -14551,7 +14769,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14551
14769
|
try {
|
|
14552
14770
|
session = await createCaptureSession9(
|
|
14553
14771
|
fileServer.url,
|
|
14554
|
-
|
|
14772
|
+
join29(workDir, "frame-cost"),
|
|
14555
14773
|
buildChunkCaptureOptions({
|
|
14556
14774
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
14557
14775
|
encoder: input,
|
|
@@ -14637,7 +14855,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
14637
14855
|
|
|
14638
14856
|
// src/services/render/stages/freezePlan.ts
|
|
14639
14857
|
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14640
|
-
import { join as
|
|
14858
|
+
import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
|
|
14641
14859
|
|
|
14642
14860
|
// src/services/distributed/planProtocol.ts
|
|
14643
14861
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -14736,7 +14954,7 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
14736
14954
|
|
|
14737
14955
|
// src/services/render/stages/planHash.ts
|
|
14738
14956
|
import { createHash as createHash6 } from "node:crypto";
|
|
14739
|
-
import { closeSync as
|
|
14957
|
+
import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
|
|
14740
14958
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
14741
14959
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
14742
14960
|
function computePlanHash(input) {
|
|
@@ -14802,7 +15020,7 @@ function sha256File(path) {
|
|
|
14802
15020
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
14803
15021
|
} while (bytesRead > 0);
|
|
14804
15022
|
} finally {
|
|
14805
|
-
|
|
15023
|
+
closeSync4(fd);
|
|
14806
15024
|
}
|
|
14807
15025
|
return hash.digest("hex");
|
|
14808
15026
|
}
|
|
@@ -14855,7 +15073,7 @@ function listPlanFiles(planDir) {
|
|
|
14855
15073
|
function walk(dir) {
|
|
14856
15074
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14857
15075
|
for (const entry of entries) {
|
|
14858
|
-
const full =
|
|
15076
|
+
const full = join30(dir, entry.name);
|
|
14859
15077
|
if (entry.isDirectory()) {
|
|
14860
15078
|
walk(full);
|
|
14861
15079
|
} else if (entry.isFile()) {
|
|
@@ -14896,15 +15114,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
14896
15114
|
function writePlanRemoteFiles(planDir, files) {
|
|
14897
15115
|
if (files.length === 0) return;
|
|
14898
15116
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
14899
|
-
writeFileSync6(
|
|
15117
|
+
writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
14900
15118
|
}
|
|
14901
15119
|
function readPlanRemoteFiles(planDir) {
|
|
14902
|
-
const path =
|
|
15120
|
+
const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
14903
15121
|
return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
14904
15122
|
}
|
|
14905
15123
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
14906
|
-
const planJsonPath =
|
|
14907
|
-
const encoderJsonPath =
|
|
15124
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
15125
|
+
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
14908
15126
|
if (!existsSync19(planJsonPath)) {
|
|
14909
15127
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
14910
15128
|
}
|
|
@@ -14941,18 +15159,18 @@ async function freezePlan(input) {
|
|
|
14941
15159
|
if (!existsSync19(planDir)) {
|
|
14942
15160
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
14943
15161
|
}
|
|
14944
|
-
const metaDir =
|
|
15162
|
+
const metaDir = join30(planDir, "meta");
|
|
14945
15163
|
if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
|
|
14946
15164
|
writeFileSync6(
|
|
14947
|
-
|
|
15165
|
+
join30(metaDir, "composition.json"),
|
|
14948
15166
|
`${JSON.stringify(composition, null, 2)}
|
|
14949
15167
|
`,
|
|
14950
15168
|
"utf-8"
|
|
14951
15169
|
);
|
|
14952
15170
|
const encoderForCanonical = stripUndefined(encoder);
|
|
14953
15171
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
14954
|
-
writeFileSync6(
|
|
14955
|
-
writeFileSync6(
|
|
15172
|
+
writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
15173
|
+
writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
14956
15174
|
`, "utf-8");
|
|
14957
15175
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
14958
15176
|
const planHash = computePlanHash({
|
|
@@ -14976,7 +15194,7 @@ async function freezePlan(input) {
|
|
|
14976
15194
|
duration: durationSeconds,
|
|
14977
15195
|
hasAudio
|
|
14978
15196
|
};
|
|
14979
|
-
const planJsonPath =
|
|
15197
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
14980
15198
|
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
14981
15199
|
`, "utf-8");
|
|
14982
15200
|
return { planJsonPath, planHash };
|
|
@@ -15018,16 +15236,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
15018
15236
|
}
|
|
15019
15237
|
|
|
15020
15238
|
// src/services/distributed/chunkAudio.ts
|
|
15021
|
-
import { closeSync as
|
|
15239
|
+
import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
15022
15240
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
15023
|
-
import { join as
|
|
15241
|
+
import { join as join32 } from "node:path";
|
|
15024
15242
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
15025
15243
|
|
|
15026
15244
|
// src/services/distributed/mp4Stitch.ts
|
|
15027
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as
|
|
15245
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync12 } from "node:fs";
|
|
15028
15246
|
import { open } from "node:fs/promises";
|
|
15029
15247
|
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
15030
|
-
import { dirname as dirname12, join as
|
|
15248
|
+
import { dirname as dirname12, join as join31 } from "node:path";
|
|
15031
15249
|
import { finished, pipeline } from "node:stream/promises";
|
|
15032
15250
|
function boxAt(src, off, at, limit) {
|
|
15033
15251
|
let size = src.readUInt32BE(off);
|
|
@@ -15393,16 +15611,16 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15393
15611
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15394
15612
|
mkdirSync11(dirname12(outputPath), { recursive: true });
|
|
15395
15613
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15396
|
-
const out = createWriteStream(
|
|
15614
|
+
const out = createWriteStream(join31(workDir, "out.mp4"));
|
|
15397
15615
|
try {
|
|
15398
15616
|
out.write(header);
|
|
15399
15617
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15400
15618
|
out.end();
|
|
15401
15619
|
await finished(out);
|
|
15402
|
-
renameWithRetry5(
|
|
15620
|
+
renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
|
|
15403
15621
|
} finally {
|
|
15404
15622
|
out.destroy();
|
|
15405
|
-
|
|
15623
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
15406
15624
|
}
|
|
15407
15625
|
}
|
|
15408
15626
|
|
|
@@ -15493,7 +15711,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
15493
15711
|
});
|
|
15494
15712
|
const bytes = readFileSync11(input.audioPath);
|
|
15495
15713
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
15496
|
-
mkdirSync12(
|
|
15714
|
+
mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
|
|
15497
15715
|
input.chunks.forEach((_, index) => {
|
|
15498
15716
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
15499
15717
|
if (!cut) return;
|
|
@@ -15501,17 +15719,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
15501
15719
|
const start = offsets[cut.firstPacket];
|
|
15502
15720
|
layout.partStarts[index] = start;
|
|
15503
15721
|
const end = offsets[last] + track.sizes[last];
|
|
15504
|
-
writeFileSync7(
|
|
15722
|
+
writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
15505
15723
|
});
|
|
15506
15724
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
15507
|
-
writeFileSync7(
|
|
15508
|
-
writeFileSync7(
|
|
15725
|
+
writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
15726
|
+
writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
15509
15727
|
}
|
|
15510
15728
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
15511
|
-
const layoutPath =
|
|
15729
|
+
const layoutPath = join32(planDir, SLICE_LAYOUT);
|
|
15512
15730
|
if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
15513
15731
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
15514
|
-
const frame = readFileSync11(
|
|
15732
|
+
const frame = readFileSync11(join32(planDir, SLICE_FRAME));
|
|
15515
15733
|
const fd = openSync5(scratchPath, "w");
|
|
15516
15734
|
try {
|
|
15517
15735
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -15524,12 +15742,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
15524
15742
|
);
|
|
15525
15743
|
const start = layout.partStarts[chunkIndex];
|
|
15526
15744
|
if (start !== void 0) {
|
|
15527
|
-
const part = readFileSync11(
|
|
15745
|
+
const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
15528
15746
|
writeSync(fd, part, 0, part.length, start);
|
|
15529
15747
|
}
|
|
15530
15748
|
ftruncateSync(fd, layout.sizeBytes);
|
|
15531
15749
|
} finally {
|
|
15532
|
-
|
|
15750
|
+
closeSync5(fd);
|
|
15533
15751
|
}
|
|
15534
15752
|
return scratchPath;
|
|
15535
15753
|
}
|
|
@@ -15580,7 +15798,7 @@ async function muxChunkWithAudio(input) {
|
|
|
15580
15798
|
// src/services/distributed/planSize.ts
|
|
15581
15799
|
import { createHash as createHash7 } from "node:crypto";
|
|
15582
15800
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
15583
|
-
import { extname as extname5, join as
|
|
15801
|
+
import { extname as extname5, join as join33, relative as relative4 } from "node:path";
|
|
15584
15802
|
var MAX_TOP_COMPONENTS = 10;
|
|
15585
15803
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
15586
15804
|
".aac",
|
|
@@ -15692,7 +15910,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
15692
15910
|
return;
|
|
15693
15911
|
}
|
|
15694
15912
|
for (const entry of entries) {
|
|
15695
|
-
const full =
|
|
15913
|
+
const full = join33(dir, entry.name);
|
|
15696
15914
|
if (entry.isDirectory()) {
|
|
15697
15915
|
walkRegularFiles(full, visit);
|
|
15698
15916
|
} else if (entry.isFile()) {
|
|
@@ -15904,13 +16122,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
15904
16122
|
}
|
|
15905
16123
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
15906
16124
|
"plan.json",
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
16125
|
+
join34("meta", "composition.json"),
|
|
16126
|
+
join34("meta", "encoder.json"),
|
|
16127
|
+
join34("meta", "chunks.json")
|
|
15910
16128
|
];
|
|
15911
16129
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
15912
16130
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
15913
|
-
|
|
16131
|
+
rmSync13(join34(planDir, relativePath), { force: true });
|
|
15914
16132
|
}
|
|
15915
16133
|
}
|
|
15916
16134
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -16052,19 +16270,19 @@ function createVideoSourceShipper(input) {
|
|
|
16052
16270
|
const known = shipped.get(srcPath);
|
|
16053
16271
|
if (known) return known;
|
|
16054
16272
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
16055
|
-
const current = stagedRel === null ? srcPath :
|
|
16273
|
+
const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
|
|
16056
16274
|
let result = null;
|
|
16057
16275
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
16058
16276
|
const rel = pathInside(root, srcPath);
|
|
16059
|
-
if (rel !== null && (existsSync21(
|
|
16277
|
+
if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
16060
16278
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
16061
16279
|
break;
|
|
16062
16280
|
}
|
|
16063
16281
|
}
|
|
16064
16282
|
if (result === null) {
|
|
16065
16283
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
16066
|
-
const destination =
|
|
16067
|
-
mkdirSync13(
|
|
16284
|
+
const destination = join34(input.planDir, ...result.split("/"));
|
|
16285
|
+
mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
|
|
16068
16286
|
try {
|
|
16069
16287
|
linkSync(current, destination);
|
|
16070
16288
|
} catch {
|
|
@@ -16113,14 +16331,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16113
16331
|
variables: config.variables
|
|
16114
16332
|
});
|
|
16115
16333
|
const entryFile = config.entryFile ?? "index.html";
|
|
16116
|
-
const htmlPath =
|
|
16334
|
+
const htmlPath = join34(projectDir, entryFile);
|
|
16117
16335
|
if (!existsSync21(htmlPath)) {
|
|
16118
16336
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16119
16337
|
}
|
|
16120
|
-
const workDir =
|
|
16121
|
-
|
|
16338
|
+
const workDir = join34(planDir, ".plan-work");
|
|
16339
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16122
16340
|
mkdirSync13(workDir, { recursive: true });
|
|
16123
|
-
const compiledDir =
|
|
16341
|
+
const compiledDir = join34(workDir, "compiled");
|
|
16124
16342
|
mkdirSync13(compiledDir, { recursive: true });
|
|
16125
16343
|
let stageStarted = Date.now();
|
|
16126
16344
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -16134,10 +16352,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16134
16352
|
}
|
|
16135
16353
|
});
|
|
16136
16354
|
logPlanStage(log, "copy", stageStarted);
|
|
16137
|
-
const finalCompiledDir =
|
|
16355
|
+
const finalCompiledDir = join34(planDir, "compiled");
|
|
16138
16356
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
16139
16357
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
16140
|
-
const remoteLocalPath = (rel) =>
|
|
16358
|
+
const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
|
|
16141
16359
|
for (const [, file] of remoteFiles) {
|
|
16142
16360
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
16143
16361
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -16323,17 +16541,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16323
16541
|
let stageStarted = Date.now();
|
|
16324
16542
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
16325
16543
|
logPlanStage(log, "extract", stageStarted);
|
|
16326
|
-
const stagedVideoFrames =
|
|
16327
|
-
const videoFramesDst =
|
|
16328
|
-
if (existsSync21(videoFramesDst))
|
|
16329
|
-
const videoSourcesDir =
|
|
16330
|
-
|
|
16544
|
+
const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
|
|
16545
|
+
const videoFramesDst = join34(planDir, "video-frames");
|
|
16546
|
+
if (existsSync21(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
|
|
16547
|
+
const videoSourcesDir = join34(planDir, "video-sources");
|
|
16548
|
+
rmSync13(videoSourcesDir, { recursive: true, force: true });
|
|
16331
16549
|
if (existsSync21(stagedVideoFrames)) {
|
|
16332
16550
|
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
16333
16551
|
} else {
|
|
16334
16552
|
mkdirSync13(videoFramesDst, { recursive: true });
|
|
16335
16553
|
}
|
|
16336
|
-
if (existsSync21(finalCompiledDir))
|
|
16554
|
+
if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
|
|
16337
16555
|
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
16338
16556
|
const shipVideoSource = createVideoSourceShipper({
|
|
16339
16557
|
planDir,
|
|
@@ -16363,7 +16581,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16363
16581
|
};
|
|
16364
16582
|
})
|
|
16365
16583
|
});
|
|
16366
|
-
mkdirSync13(
|
|
16584
|
+
mkdirSync13(join34(planDir, "meta"), { recursive: true });
|
|
16367
16585
|
writePlanRemoteFiles(
|
|
16368
16586
|
planDir,
|
|
16369
16587
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -16373,7 +16591,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16373
16591
|
}))
|
|
16374
16592
|
);
|
|
16375
16593
|
writeFileSync8(
|
|
16376
|
-
|
|
16594
|
+
join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
16377
16595
|
JSON.stringify(planVideosJson, null, 2),
|
|
16378
16596
|
"utf-8"
|
|
16379
16597
|
);
|
|
@@ -16452,7 +16670,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16452
16670
|
if (audioResult?.audioError) {
|
|
16453
16671
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
16454
16672
|
}
|
|
16455
|
-
const planAudioPath =
|
|
16673
|
+
const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16456
16674
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16457
16675
|
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
16458
16676
|
}
|
|
@@ -16501,7 +16719,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16501
16719
|
format: config.format
|
|
16502
16720
|
};
|
|
16503
16721
|
try {
|
|
16504
|
-
|
|
16722
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16505
16723
|
} catch (err) {
|
|
16506
16724
|
log.warn("[plan] failed to remove temp work dir", {
|
|
16507
16725
|
workDir,
|
|
@@ -16876,6 +17094,7 @@ function assertRequestOptionScalars(options) {
|
|
|
16876
17094
|
assertOptionalInteger(options, "gifLoop");
|
|
16877
17095
|
assertOptionalInteger(options, "hlsSegmentSeconds", 1);
|
|
16878
17096
|
assertOptionalInteger(options, "workers", 1);
|
|
17097
|
+
assertOptionalInteger(options, "maxRenderMs");
|
|
16879
17098
|
assertOptionalInteger(options, "crf");
|
|
16880
17099
|
for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
|
|
16881
17100
|
assertOptionalBoolean(options, field);
|
|
@@ -17072,13 +17291,13 @@ import {
|
|
|
17072
17291
|
import {
|
|
17073
17292
|
existsSync as existsSync24,
|
|
17074
17293
|
mkdirSync as mkdirSync14,
|
|
17075
|
-
statSync as
|
|
17294
|
+
statSync as statSync9,
|
|
17076
17295
|
mkdtempSync as mkdtempSync5,
|
|
17077
17296
|
writeFileSync as writeFileSync9,
|
|
17078
|
-
rmSync as
|
|
17297
|
+
rmSync as rmSync14,
|
|
17079
17298
|
createReadStream as createReadStream5
|
|
17080
17299
|
} from "node:fs";
|
|
17081
|
-
import { resolve as resolve10, dirname as dirname14, join as
|
|
17300
|
+
import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
|
|
17082
17301
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17083
17302
|
import { parseArgs } from "node:util";
|
|
17084
17303
|
import crypto from "node:crypto";
|
|
@@ -17091,13 +17310,13 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
17091
17310
|
import {
|
|
17092
17311
|
existsSync as existsSync22,
|
|
17093
17312
|
readFileSync as readFileSync12,
|
|
17094
|
-
statSync as
|
|
17313
|
+
statSync as statSync8,
|
|
17095
17314
|
openSync as openSync6,
|
|
17096
|
-
fstatSync as
|
|
17097
|
-
closeSync as
|
|
17315
|
+
fstatSync as fstatSync3,
|
|
17316
|
+
closeSync as closeSync6,
|
|
17098
17317
|
constants as constants3
|
|
17099
17318
|
} from "node:fs";
|
|
17100
|
-
import { resolve as resolve9, join as
|
|
17319
|
+
import { resolve as resolve9, join as join35, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17101
17320
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
17102
17321
|
function isStringRecord(value) {
|
|
17103
17322
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -17131,19 +17350,19 @@ function readEntryHtml(filePath) {
|
|
|
17131
17350
|
} catch (error) {
|
|
17132
17351
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
17133
17352
|
return null;
|
|
17134
|
-
if (!
|
|
17353
|
+
if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
17135
17354
|
throw error;
|
|
17136
17355
|
}
|
|
17137
17356
|
try {
|
|
17138
|
-
if (!
|
|
17357
|
+
if (!fstatSync3(fd).isFile()) return null;
|
|
17139
17358
|
return readFileSync12(fd, "utf-8");
|
|
17140
17359
|
} finally {
|
|
17141
|
-
|
|
17360
|
+
closeSync6(fd);
|
|
17142
17361
|
}
|
|
17143
17362
|
}
|
|
17144
17363
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
17145
17364
|
const absProjectDir = resolve9(projectDir);
|
|
17146
|
-
if (!existsSync22(absProjectDir) || !
|
|
17365
|
+
if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
17147
17366
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17148
17367
|
}
|
|
17149
17368
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -17165,7 +17384,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
17165
17384
|
}
|
|
17166
17385
|
}
|
|
17167
17386
|
return {
|
|
17168
|
-
error: `No HTML entry file found in project directory: ${
|
|
17387
|
+
error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
|
|
17169
17388
|
};
|
|
17170
17389
|
}
|
|
17171
17390
|
function prepareHyperframeLintBody(body) {
|
|
@@ -17211,7 +17430,7 @@ async function runHyperframeLint(prepared) {
|
|
|
17211
17430
|
// src/services/healthWorker.ts
|
|
17212
17431
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
17213
17432
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17214
|
-
import { dirname as dirname13, join as
|
|
17433
|
+
import { dirname as dirname13, join as join36 } from "node:path";
|
|
17215
17434
|
import { existsSync as existsSync23 } from "node:fs";
|
|
17216
17435
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
17217
17436
|
async function startHealthWorker(options = {}) {
|
|
@@ -17287,7 +17506,7 @@ function defaultLogger2() {
|
|
|
17287
17506
|
}
|
|
17288
17507
|
function resolveWorkerEntry2() {
|
|
17289
17508
|
const here = dirname13(fileURLToPath6(import.meta.url));
|
|
17290
|
-
const candidates = [
|
|
17509
|
+
const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
|
|
17291
17510
|
for (const candidate of candidates) {
|
|
17292
17511
|
if (existsSync23(candidate)) return candidate;
|
|
17293
17512
|
}
|
|
@@ -17308,7 +17527,8 @@ var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
17308
17527
|
"INVALID_VIDEO_METADATA",
|
|
17309
17528
|
"VIDEO_SOURCE_UNRENDERABLE",
|
|
17310
17529
|
"VIDEO_EXTRACTION_FAILED",
|
|
17311
|
-
"ENCODER_INTERRUPTED"
|
|
17530
|
+
"ENCODER_INTERRUPTED",
|
|
17531
|
+
"RENDER_TOO_SLOW"
|
|
17312
17532
|
]);
|
|
17313
17533
|
function extractSafeRenderErrorCode(error) {
|
|
17314
17534
|
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
@@ -17367,6 +17587,7 @@ function parseRenderOptions(body) {
|
|
|
17367
17587
|
const fps = parseServerFps(body.fps);
|
|
17368
17588
|
const quality = parseServerQuality(body.quality);
|
|
17369
17589
|
const workers = typeof body.workers === "number" ? body.workers : void 0;
|
|
17590
|
+
const maxRenderMs = typeof body.maxRenderMs === "number" ? body.maxRenderMs : void 0;
|
|
17370
17591
|
const useGpu = body.gpu === true;
|
|
17371
17592
|
const debug = body.debug === true;
|
|
17372
17593
|
const strictness = body.bestEffort === false ? "strict" : "best-effort";
|
|
@@ -17382,6 +17603,7 @@ function parseRenderOptions(body) {
|
|
|
17382
17603
|
fps,
|
|
17383
17604
|
quality,
|
|
17384
17605
|
workers,
|
|
17606
|
+
maxRenderMs,
|
|
17385
17607
|
crf,
|
|
17386
17608
|
videoBitrate,
|
|
17387
17609
|
useGpu,
|
|
@@ -17415,6 +17637,7 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
17415
17637
|
quality: input.quality,
|
|
17416
17638
|
format: input.format ?? "mp4",
|
|
17417
17639
|
workers: input.workers,
|
|
17640
|
+
maxRenderMs: input.maxRenderMs,
|
|
17418
17641
|
crf: input.crf,
|
|
17419
17642
|
videoBitrate: input.videoBitrate,
|
|
17420
17643
|
useGpu: input.useGpu,
|
|
@@ -17441,6 +17664,9 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
17441
17664
|
function validateRenderOverrides(body) {
|
|
17442
17665
|
const encoder = parseEncoderOptions(body, parseServerFormat(body.format) ?? "mp4");
|
|
17443
17666
|
if (!encoder.ok) return encoder.error;
|
|
17667
|
+
if (body.maxRenderMs !== void 0 && (typeof body.maxRenderMs !== "number" || !Number.isInteger(body.maxRenderMs) || body.maxRenderMs < 0)) {
|
|
17668
|
+
return "maxRenderMs must be a non-negative integer number of milliseconds";
|
|
17669
|
+
}
|
|
17444
17670
|
if (body.variables !== void 0 && !isPlainObject2(body.variables)) {
|
|
17445
17671
|
return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
|
|
17446
17672
|
}
|
|
@@ -17473,7 +17699,7 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
17473
17699
|
const candidate = nonEmptyString(projectDir);
|
|
17474
17700
|
if (!candidate) return null;
|
|
17475
17701
|
const absProjectDir = resolve10(candidate);
|
|
17476
|
-
if (!existsSync24(absProjectDir) || !
|
|
17702
|
+
if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
17477
17703
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17478
17704
|
}
|
|
17479
17705
|
const entry = options.entryFile || "index.html";
|
|
@@ -17498,8 +17724,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17498
17724
|
}
|
|
17499
17725
|
function materializeInlineProject(html, options) {
|
|
17500
17726
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
17501
|
-
const tempProjectDir = mkdtempSync5(
|
|
17502
|
-
writeFileSync9(
|
|
17727
|
+
const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
|
|
17728
|
+
writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
|
|
17503
17729
|
return {
|
|
17504
17730
|
prepared: {
|
|
17505
17731
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -17560,7 +17786,7 @@ function createArtifactStore(ttlMs) {
|
|
|
17560
17786
|
function cleanupTempDir(dir, log) {
|
|
17561
17787
|
if (!dir) return;
|
|
17562
17788
|
try {
|
|
17563
|
-
|
|
17789
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
17564
17790
|
} catch (error) {
|
|
17565
17791
|
log.warn("Failed to cleanup temp project dir", {
|
|
17566
17792
|
cleanupProjectDir: dir,
|
|
@@ -17569,7 +17795,7 @@ function cleanupTempDir(dir, log) {
|
|
|
17569
17795
|
}
|
|
17570
17796
|
}
|
|
17571
17797
|
function outputFileSize(path) {
|
|
17572
|
-
return existsSync24(path) ?
|
|
17798
|
+
return existsSync24(path) ? statSync9(path).size : 0;
|
|
17573
17799
|
}
|
|
17574
17800
|
function createBlockingProgressReporter(log, requestId) {
|
|
17575
17801
|
let lastLoggedPct = -10;
|
|
@@ -17869,7 +18095,7 @@ function createRenderHandlers(options = {}) {
|
|
|
17869
18095
|
store.delete(token);
|
|
17870
18096
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
17871
18097
|
}
|
|
17872
|
-
const stats =
|
|
18098
|
+
const stats = statSync9(artifact.path);
|
|
17873
18099
|
return new Response(createReadStream5(artifact.path), {
|
|
17874
18100
|
headers: {
|
|
17875
18101
|
"content-type": "video/mp4",
|
|
@@ -17968,12 +18194,12 @@ import {
|
|
|
17968
18194
|
mkdtempSync as mkdtempSync7,
|
|
17969
18195
|
readFileSync as readFileSync13,
|
|
17970
18196
|
readdirSync as readdirSync9,
|
|
17971
|
-
rmSync as
|
|
17972
|
-
statSync as
|
|
18197
|
+
rmSync as rmSync16,
|
|
18198
|
+
statSync as statSync11,
|
|
17973
18199
|
writeFileSync as writeFileSync11
|
|
17974
18200
|
} from "node:fs";
|
|
17975
18201
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
17976
|
-
import { dirname as dirname16, isAbsolute as isAbsolute5, join as
|
|
18202
|
+
import { dirname as dirname16, isAbsolute as isAbsolute5, join as join40, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
|
|
17977
18203
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
17978
18204
|
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
17979
18205
|
|
|
@@ -17984,12 +18210,12 @@ import {
|
|
|
17984
18210
|
linkSync as linkSync2,
|
|
17985
18211
|
mkdirSync as mkdirSync15,
|
|
17986
18212
|
mkdtempSync as mkdtempSync6,
|
|
17987
|
-
rmSync as
|
|
17988
|
-
statSync as
|
|
18213
|
+
rmSync as rmSync15,
|
|
18214
|
+
statSync as statSync10,
|
|
17989
18215
|
writeFileSync as writeFileSync10
|
|
17990
18216
|
} from "node:fs";
|
|
17991
18217
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
17992
|
-
import { dirname as dirname15, join as
|
|
18218
|
+
import { dirname as dirname15, join as join39 } from "node:path";
|
|
17993
18219
|
|
|
17994
18220
|
// src/services/distributed/planV2Errors.ts
|
|
17995
18221
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -18004,7 +18230,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
18004
18230
|
};
|
|
18005
18231
|
|
|
18006
18232
|
// src/services/distributed/planV2Layout.ts
|
|
18007
|
-
import { join as
|
|
18233
|
+
import { join as join38 } from "node:path";
|
|
18008
18234
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18009
18235
|
function assertPlanV2Sha256(value, field) {
|
|
18010
18236
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -18014,7 +18240,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
18014
18240
|
}
|
|
18015
18241
|
function planV2BlobPath(root, sha256) {
|
|
18016
18242
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
18017
|
-
return
|
|
18243
|
+
return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
18018
18244
|
}
|
|
18019
18245
|
|
|
18020
18246
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18055,11 +18281,11 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18055
18281
|
this.destinationDir = destinationDir;
|
|
18056
18282
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18057
18283
|
mkdirSync15(dirname15(destinationDir), { recursive: true });
|
|
18058
|
-
this.temporaryDir = mkdtempSync6(
|
|
18284
|
+
this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
|
|
18059
18285
|
}
|
|
18060
18286
|
async putBlob(blob) {
|
|
18061
18287
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
18062
|
-
const sourceSize =
|
|
18288
|
+
const sourceSize = statSync10(blob.sourcePath).size;
|
|
18063
18289
|
if (sourceSize !== blob.sizeBytes) {
|
|
18064
18290
|
throw new PlanV2IntegrityError(
|
|
18065
18291
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -18068,8 +18294,8 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18068
18294
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18069
18295
|
if (existsSync25(destinationPath)) return;
|
|
18070
18296
|
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
18071
|
-
const stagingDir = mkdtempSync6(
|
|
18072
|
-
const temporaryPath =
|
|
18297
|
+
const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18298
|
+
const temporaryPath = join39(stagingDir, "blob");
|
|
18073
18299
|
try {
|
|
18074
18300
|
try {
|
|
18075
18301
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -18079,7 +18305,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18079
18305
|
}
|
|
18080
18306
|
renameWithRetry7(temporaryPath, destinationPath);
|
|
18081
18307
|
} finally {
|
|
18082
|
-
|
|
18308
|
+
rmSync15(stagingDir, { recursive: true, force: true });
|
|
18083
18309
|
}
|
|
18084
18310
|
}
|
|
18085
18311
|
async commitManifest(manifestBytes) {
|
|
@@ -18090,13 +18316,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18090
18316
|
);
|
|
18091
18317
|
}
|
|
18092
18318
|
}
|
|
18093
|
-
writeFileSync10(
|
|
18319
|
+
writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18094
18320
|
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
18095
18321
|
this.#committed = true;
|
|
18096
18322
|
}
|
|
18097
18323
|
async abort() {
|
|
18098
18324
|
if (!this.#committed) {
|
|
18099
|
-
|
|
18325
|
+
rmSync15(this.temporaryDir, { recursive: true, force: true });
|
|
18100
18326
|
}
|
|
18101
18327
|
}
|
|
18102
18328
|
};
|
|
@@ -18135,7 +18361,7 @@ function listFiles(root) {
|
|
|
18135
18361
|
const rootResolved = resolve11(root);
|
|
18136
18362
|
function walk(dir) {
|
|
18137
18363
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18138
|
-
const absolutePath =
|
|
18364
|
+
const absolutePath = join40(dir, entry.name);
|
|
18139
18365
|
if (entry.isDirectory()) {
|
|
18140
18366
|
walk(absolutePath);
|
|
18141
18367
|
} else if (entry.isFile()) {
|
|
@@ -18162,13 +18388,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18162
18388
|
}
|
|
18163
18389
|
}
|
|
18164
18390
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18165
|
-
const videoRoot =
|
|
18391
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18166
18392
|
if (!existsSync26(videoRoot)) return;
|
|
18167
18393
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18168
18394
|
if (!videoEntry.isDirectory()) continue;
|
|
18169
|
-
const videoDir =
|
|
18395
|
+
const videoDir = join40(videoRoot, videoEntry.name);
|
|
18170
18396
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18171
|
-
const sentinelPath =
|
|
18397
|
+
const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18172
18398
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18173
18399
|
}
|
|
18174
18400
|
}
|
|
@@ -18215,7 +18441,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18215
18441
|
if (video.deferredRange) {
|
|
18216
18442
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18217
18443
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18218
|
-
framePaths2.set(index,
|
|
18444
|
+
framePaths2.set(index, join40(outputDir, String(index)));
|
|
18219
18445
|
}
|
|
18220
18446
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18221
18447
|
}
|
|
@@ -18223,7 +18449,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18223
18449
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18224
18450
|
for (const frameName of frameNames) {
|
|
18225
18451
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18226
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18452
|
+
assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
|
|
18227
18453
|
continue;
|
|
18228
18454
|
}
|
|
18229
18455
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18242,7 +18468,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18242
18468
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18243
18469
|
);
|
|
18244
18470
|
}
|
|
18245
|
-
framePaths.set(frameIndex,
|
|
18471
|
+
framePaths.set(frameIndex, join40(outputDir, frameName));
|
|
18246
18472
|
}
|
|
18247
18473
|
return {
|
|
18248
18474
|
...video,
|
|
@@ -18263,7 +18489,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18263
18489
|
}
|
|
18264
18490
|
}
|
|
18265
18491
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18266
|
-
const videosPath =
|
|
18492
|
+
const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18267
18493
|
if (!existsSync26(videosPath)) return;
|
|
18268
18494
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18269
18495
|
for (const video of videos.extracted) {
|
|
@@ -18292,13 +18518,13 @@ function parseChunkSlices(value) {
|
|
|
18292
18518
|
});
|
|
18293
18519
|
}
|
|
18294
18520
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18295
|
-
const videoRoot =
|
|
18521
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18296
18522
|
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18297
|
-
const videosPath =
|
|
18523
|
+
const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18298
18524
|
if (!existsSync26(videosPath)) {
|
|
18299
18525
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18300
18526
|
}
|
|
18301
|
-
const chunksPath =
|
|
18527
|
+
const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
|
|
18302
18528
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18303
18529
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18304
18530
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18354,20 +18580,20 @@ function computeManifestHash(manifest) {
|
|
|
18354
18580
|
function writeBlob(sourcePath, destinationPath) {
|
|
18355
18581
|
if (existsSync26(destinationPath)) return;
|
|
18356
18582
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18357
|
-
const temporaryDir = mkdtempSync7(
|
|
18358
|
-
const temporaryPath =
|
|
18583
|
+
const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18584
|
+
const temporaryPath = join40(temporaryDir, "blob");
|
|
18359
18585
|
try {
|
|
18360
18586
|
copyFileSync7(sourcePath, temporaryPath);
|
|
18361
18587
|
renameWithRetry8(temporaryPath, destinationPath);
|
|
18362
18588
|
} finally {
|
|
18363
|
-
|
|
18589
|
+
rmSync16(temporaryDir, { recursive: true, force: true });
|
|
18364
18590
|
}
|
|
18365
18591
|
}
|
|
18366
18592
|
function publishBlobFor(absolutePath, digests) {
|
|
18367
18593
|
return {
|
|
18368
18594
|
sourcePath: absolutePath,
|
|
18369
18595
|
sha256: planFileSha256(absolutePath, digests),
|
|
18370
|
-
sizeBytes:
|
|
18596
|
+
sizeBytes: statSync11(absolutePath).size
|
|
18371
18597
|
};
|
|
18372
18598
|
}
|
|
18373
18599
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18391,7 +18617,7 @@ async function putTasks(tasks) {
|
|
|
18391
18617
|
if (failure) throw failure.reason;
|
|
18392
18618
|
}
|
|
18393
18619
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18394
|
-
const executionPlanPath =
|
|
18620
|
+
const executionPlanPath = join40(executionPlanDir, "plan.json");
|
|
18395
18621
|
if (!existsSync26(executionPlanPath)) {
|
|
18396
18622
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18397
18623
|
}
|
|
@@ -18419,7 +18645,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18419
18645
|
}
|
|
18420
18646
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18421
18647
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18422
|
-
const encoder = readJsonFile(
|
|
18648
|
+
const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18423
18649
|
const files = listFiles(executionPlanDir);
|
|
18424
18650
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18425
18651
|
for (const file of files) {
|
|
@@ -18480,20 +18706,20 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18480
18706
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18481
18707
|
}
|
|
18482
18708
|
mkdirSync16(dirname16(planV2Dir), { recursive: true });
|
|
18483
|
-
const tempDir = mkdtempSync7(
|
|
18709
|
+
const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
|
|
18484
18710
|
try {
|
|
18485
18711
|
for (const blob of publication.blobs) {
|
|
18486
18712
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18487
18713
|
}
|
|
18488
18714
|
writeFileSync11(
|
|
18489
|
-
|
|
18715
|
+
join40(tempDir, "plan.json"),
|
|
18490
18716
|
canonicalJsonStringify(publication.manifest),
|
|
18491
18717
|
"utf-8"
|
|
18492
18718
|
);
|
|
18493
18719
|
renameWithRetry8(tempDir, planV2Dir);
|
|
18494
18720
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18495
18721
|
} catch (error) {
|
|
18496
|
-
|
|
18722
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18497
18723
|
throw error;
|
|
18498
18724
|
}
|
|
18499
18725
|
}
|
|
@@ -18533,7 +18759,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18533
18759
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18534
18760
|
}
|
|
18535
18761
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18536
|
-
const stagingRoot = mkdtempSync7(
|
|
18762
|
+
const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
18537
18763
|
const digests = /* @__PURE__ */ new Map();
|
|
18538
18764
|
try {
|
|
18539
18765
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18544,7 +18770,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18544
18770
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18545
18771
|
whileAudioMixes: () => putBlobs(
|
|
18546
18772
|
publisher,
|
|
18547
|
-
listFiles(
|
|
18773
|
+
listFiles(join40(stagingRoot, "compiled")).map(
|
|
18548
18774
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18549
18775
|
)
|
|
18550
18776
|
),
|
|
@@ -18560,7 +18786,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18560
18786
|
}
|
|
18561
18787
|
throw error;
|
|
18562
18788
|
} finally {
|
|
18563
|
-
|
|
18789
|
+
rmSync16(stagingRoot, { recursive: true, force: true });
|
|
18564
18790
|
}
|
|
18565
18791
|
}
|
|
18566
18792
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -18576,7 +18802,7 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
18576
18802
|
function resultFromManifest(planV2Dir, manifest) {
|
|
18577
18803
|
return {
|
|
18578
18804
|
planDir: planV2Dir,
|
|
18579
|
-
manifestPath:
|
|
18805
|
+
manifestPath: join40(planV2Dir, "plan.json"),
|
|
18580
18806
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
18581
18807
|
planHash: manifest.planHash,
|
|
18582
18808
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -18712,7 +18938,7 @@ function parsePlanV2Manifest(value) {
|
|
|
18712
18938
|
return parsed;
|
|
18713
18939
|
}
|
|
18714
18940
|
function readPlanV2Manifest(planV2Dir) {
|
|
18715
|
-
const manifestPath =
|
|
18941
|
+
const manifestPath = join40(planV2Dir, "plan.json");
|
|
18716
18942
|
if (!existsSync26(manifestPath)) {
|
|
18717
18943
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18718
18944
|
}
|
|
@@ -18735,7 +18961,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18735
18961
|
if (!existsSync26(sourcePath)) {
|
|
18736
18962
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18737
18963
|
}
|
|
18738
|
-
const stats =
|
|
18964
|
+
const stats = statSync11(sourcePath);
|
|
18739
18965
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
18740
18966
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
18741
18967
|
}
|
|
@@ -18758,16 +18984,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18758
18984
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
18759
18985
|
}));
|
|
18760
18986
|
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18761
|
-
const tempDir = mkdtempSync7(
|
|
18987
|
+
const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
|
|
18762
18988
|
try {
|
|
18763
18989
|
for (const { artifact, sourcePath } of verified) {
|
|
18764
|
-
const destinationPath =
|
|
18990
|
+
const destinationPath = join40(tempDir, ...artifact.path.split("/"));
|
|
18765
18991
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18766
18992
|
copyFileSync7(sourcePath, destinationPath);
|
|
18767
18993
|
}
|
|
18768
18994
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
18769
18995
|
writeFileSync11(
|
|
18770
|
-
|
|
18996
|
+
join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
18771
18997
|
canonicalJsonStringify({
|
|
18772
18998
|
manifest,
|
|
18773
18999
|
target,
|
|
@@ -18777,7 +19003,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18777
19003
|
);
|
|
18778
19004
|
renameWithRetry8(tempDir, destinationDir);
|
|
18779
19005
|
} catch (error) {
|
|
18780
|
-
|
|
19006
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18781
19007
|
throw error;
|
|
18782
19008
|
}
|
|
18783
19009
|
return {
|
|
@@ -18790,7 +19016,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18790
19016
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
18791
19017
|
// before the container change carries the legacy name, and materializing it
|
|
18792
19018
|
// under the new one would point at a file that was never written.
|
|
18793
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19019
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
|
|
18794
19020
|
};
|
|
18795
19021
|
}
|
|
18796
19022
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -18809,7 +19035,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
18809
19035
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
18810
19036
|
}
|
|
18811
19037
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
18812
|
-
const markerPath =
|
|
19038
|
+
const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
18813
19039
|
if (!existsSync26(markerPath)) return null;
|
|
18814
19040
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
18815
19041
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -18827,8 +19053,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18827
19053
|
assertRemoteSources(remoteSources, required);
|
|
18828
19054
|
for (const artifact of required) {
|
|
18829
19055
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
18830
|
-
const path =
|
|
18831
|
-
if (!existsSync26(path) ||
|
|
19056
|
+
const path = join40(planDir, ...artifact.path.split("/"));
|
|
19057
|
+
if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
|
|
18832
19058
|
throw new PlanV2IntegrityError(
|
|
18833
19059
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
18834
19060
|
);
|
|
@@ -18841,9 +19067,9 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18841
19067
|
}
|
|
18842
19068
|
|
|
18843
19069
|
// src/services/distributed/planV2Execution.ts
|
|
18844
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19070
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
|
|
18845
19071
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
18846
|
-
import { join as
|
|
19072
|
+
import { join as join43 } from "node:path";
|
|
18847
19073
|
|
|
18848
19074
|
// src/services/distributed/assemble.ts
|
|
18849
19075
|
import {
|
|
@@ -18853,11 +19079,11 @@ import {
|
|
|
18853
19079
|
mkdtempSync as mkdtempSync8,
|
|
18854
19080
|
readFileSync as readFileSync14,
|
|
18855
19081
|
readdirSync as readdirSync10,
|
|
18856
|
-
rmSync as
|
|
18857
|
-
statSync as
|
|
19082
|
+
rmSync as rmSync17,
|
|
19083
|
+
statSync as statSync12,
|
|
18858
19084
|
writeFileSync as writeFileSync12
|
|
18859
19085
|
} from "node:fs";
|
|
18860
|
-
import { dirname as dirname17, join as
|
|
19086
|
+
import { dirname as dirname17, join as join41 } from "node:path";
|
|
18861
19087
|
import {
|
|
18862
19088
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
18863
19089
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -18893,7 +19119,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18893
19119
|
outputPath,
|
|
18894
19120
|
durationMs: Date.now() - start,
|
|
18895
19121
|
framesEncoded: plan2.totalFrames,
|
|
18896
|
-
fileSize:
|
|
19122
|
+
fileSize: statSync12(outputPath).size
|
|
18897
19123
|
};
|
|
18898
19124
|
}
|
|
18899
19125
|
const format = plan2.dimensions.format;
|
|
@@ -18921,7 +19147,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18921
19147
|
};
|
|
18922
19148
|
try {
|
|
18923
19149
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
18924
|
-
const finalPath =
|
|
19150
|
+
const finalPath = join41(workDir, `output.${format}`);
|
|
18925
19151
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
18926
19152
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
18927
19153
|
renameWithRetry9(finalPath, outputPath);
|
|
@@ -18932,7 +19158,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18932
19158
|
});
|
|
18933
19159
|
} finally {
|
|
18934
19160
|
try {
|
|
18935
|
-
|
|
19161
|
+
rmSync17(workDir, { recursive: true, force: true });
|
|
18936
19162
|
} catch (err) {
|
|
18937
19163
|
log.warn("[assemble] failed to remove work dir", {
|
|
18938
19164
|
workDir,
|
|
@@ -18940,7 +19166,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18940
19166
|
});
|
|
18941
19167
|
}
|
|
18942
19168
|
}
|
|
18943
|
-
const fileSize = existsSync27(outputPath) ?
|
|
19169
|
+
const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
|
|
18944
19170
|
return {
|
|
18945
19171
|
outputPath,
|
|
18946
19172
|
durationMs: Date.now() - start,
|
|
@@ -18949,8 +19175,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18949
19175
|
};
|
|
18950
19176
|
}
|
|
18951
19177
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
18952
|
-
const planJsonPath =
|
|
18953
|
-
const chunksJsonPath =
|
|
19178
|
+
const planJsonPath = join41(planDir, "plan.json");
|
|
19179
|
+
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
18954
19180
|
if (!existsSync27(planJsonPath)) {
|
|
18955
19181
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
18956
19182
|
}
|
|
@@ -18998,7 +19224,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
18998
19224
|
return args;
|
|
18999
19225
|
}
|
|
19000
19226
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
19001
|
-
const cfrOutputPath =
|
|
19227
|
+
const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
|
|
19002
19228
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
19003
19229
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
19004
19230
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -19025,7 +19251,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19025
19251
|
);
|
|
19026
19252
|
return;
|
|
19027
19253
|
}
|
|
19028
|
-
const paddedAudioPath =
|
|
19254
|
+
const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
|
|
19029
19255
|
const padTrimResult = await ctx.timed(
|
|
19030
19256
|
"audioMs",
|
|
19031
19257
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -19065,7 +19291,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19065
19291
|
}
|
|
19066
19292
|
}
|
|
19067
19293
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
19068
|
-
const listPath =
|
|
19294
|
+
const listPath = join41(workDir, "concat-list.txt");
|
|
19069
19295
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
19070
19296
|
writeFileSync12(listPath, `${body}
|
|
19071
19297
|
`, "utf-8");
|
|
@@ -19103,11 +19329,11 @@ function stitchesSeparateAudio(planDir) {
|
|
|
19103
19329
|
return lockedAudio(planDir).separateAudio === true;
|
|
19104
19330
|
}
|
|
19105
19331
|
function planFormat(planDir) {
|
|
19106
|
-
const plan2 = JSON.parse(readFileSync14(
|
|
19332
|
+
const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
|
|
19107
19333
|
return plan2.dimensions.format;
|
|
19108
19334
|
}
|
|
19109
19335
|
function lockedAudio(planDir) {
|
|
19110
|
-
const encoderJsonPath =
|
|
19336
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19111
19337
|
if (!existsSync27(encoderJsonPath)) return {};
|
|
19112
19338
|
return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
|
|
19113
19339
|
}
|
|
@@ -19117,7 +19343,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
19117
19343
|
`[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
19344
|
);
|
|
19119
19345
|
}
|
|
19120
|
-
const encoderJsonPath =
|
|
19346
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19121
19347
|
if (!existsSync27(encoderJsonPath)) {
|
|
19122
19348
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
19123
19349
|
}
|
|
@@ -19130,11 +19356,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19130
19356
|
return encoderJson;
|
|
19131
19357
|
}
|
|
19132
19358
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19133
|
-
if (existsSync27(outputPath))
|
|
19359
|
+
if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
|
|
19134
19360
|
mkdirSync17(outputPath, { recursive: true });
|
|
19135
19361
|
let globalIdx = 0;
|
|
19136
19362
|
for (const chunkDir of chunkPaths) {
|
|
19137
|
-
if (!
|
|
19363
|
+
if (!statSync12(chunkDir).isDirectory()) {
|
|
19138
19364
|
throw new Error(
|
|
19139
19365
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
19140
19366
|
);
|
|
@@ -19144,8 +19370,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19144
19370
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
19145
19371
|
}
|
|
19146
19372
|
for (const frame of frames) {
|
|
19147
|
-
const dst =
|
|
19148
|
-
cpSync3(
|
|
19373
|
+
const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
19374
|
+
cpSync3(join41(chunkDir, frame), dst);
|
|
19149
19375
|
globalIdx += 1;
|
|
19150
19376
|
}
|
|
19151
19377
|
}
|
|
@@ -19155,13 +19381,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19155
19381
|
);
|
|
19156
19382
|
}
|
|
19157
19383
|
if (audioPath !== null && existsSync27(audioPath)) {
|
|
19158
|
-
const sidecar =
|
|
19384
|
+
const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
|
|
19159
19385
|
cpSync3(audioPath, sidecar);
|
|
19160
19386
|
}
|
|
19161
19387
|
let fileSize = 0;
|
|
19162
19388
|
for (const name of readdirSync10(outputPath)) {
|
|
19163
19389
|
try {
|
|
19164
|
-
fileSize +=
|
|
19390
|
+
fileSize += statSync12(join41(outputPath, name)).size;
|
|
19165
19391
|
} catch {
|
|
19166
19392
|
}
|
|
19167
19393
|
}
|
|
@@ -19175,9 +19401,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19175
19401
|
|
|
19176
19402
|
// src/services/distributed/renderChunk.ts
|
|
19177
19403
|
import { randomBytes } from "node:crypto";
|
|
19178
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as
|
|
19404
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync18, writeFileSync as writeFileSync13 } from "node:fs";
|
|
19179
19405
|
import { cpus as cpus3 } from "node:os";
|
|
19180
|
-
import { join as
|
|
19406
|
+
import { join as join42 } from "node:path";
|
|
19181
19407
|
import {
|
|
19182
19408
|
assertSwiftShader,
|
|
19183
19409
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -19296,7 +19522,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
19296
19522
|
if (kind === "file") return sha256Hex(readFileSync15(outputPath));
|
|
19297
19523
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19298
19524
|
const lines = entries.map(
|
|
19299
|
-
(name) => `${name}\0${sha256Hex(readFileSync15(
|
|
19525
|
+
(name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
|
|
19300
19526
|
);
|
|
19301
19527
|
return sha256Hex(lines.join("\0"));
|
|
19302
19528
|
}
|
|
@@ -19313,9 +19539,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19313
19539
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19314
19540
|
const start = Date.now();
|
|
19315
19541
|
const log = defaultLogger;
|
|
19316
|
-
const planJsonPath =
|
|
19317
|
-
const encoderJsonPath =
|
|
19318
|
-
const chunksJsonPath =
|
|
19542
|
+
const planJsonPath = join42(planDir, "plan.json");
|
|
19543
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19544
|
+
const chunksJsonPath = join42(planDir, "meta", "chunks.json");
|
|
19319
19545
|
if (!existsSync28(planJsonPath)) {
|
|
19320
19546
|
throw new RenderChunkValidationError(
|
|
19321
19547
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19341,7 +19567,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19341
19567
|
}
|
|
19342
19568
|
const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19343
19569
|
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
19344
|
-
const videosJsonPath =
|
|
19570
|
+
const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19345
19571
|
let planVideos = null;
|
|
19346
19572
|
if (existsSync28(videosJsonPath)) {
|
|
19347
19573
|
try {
|
|
@@ -19373,7 +19599,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19373
19599
|
}
|
|
19374
19600
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19375
19601
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19376
|
-
const compiledDir =
|
|
19602
|
+
const compiledDir = join42(planDir, "compiled");
|
|
19377
19603
|
if (!existsSync28(compiledDir)) {
|
|
19378
19604
|
throw new RenderChunkValidationError(
|
|
19379
19605
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19431,7 +19657,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19431
19657
|
};
|
|
19432
19658
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19433
19659
|
mkdirSync18(workDir, { recursive: true });
|
|
19434
|
-
const framesDir =
|
|
19660
|
+
const framesDir = join42(workDir, "captured-frames");
|
|
19435
19661
|
mkdirSync18(framesDir, { recursive: true });
|
|
19436
19662
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19437
19663
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
@@ -19456,7 +19682,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19456
19682
|
dimensions: plan2.dimensions,
|
|
19457
19683
|
encoder,
|
|
19458
19684
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19459
|
-
html: readFileSync15(
|
|
19685
|
+
html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
|
|
19460
19686
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19461
19687
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19462
19688
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19581,7 +19807,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19581
19807
|
if (isPngSequence) {
|
|
19582
19808
|
if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
|
|
19583
19809
|
} else {
|
|
19584
|
-
const outDir =
|
|
19810
|
+
const outDir = join42(outputChunkPath, "..");
|
|
19585
19811
|
if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
|
|
19586
19812
|
}
|
|
19587
19813
|
const encodeInput = {
|
|
@@ -19685,7 +19911,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19685
19911
|
return observedChunkCaptureMode(capturePerfs);
|
|
19686
19912
|
},
|
|
19687
19913
|
resetForScreenshotRetry: () => {
|
|
19688
|
-
|
|
19914
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19689
19915
|
mkdirSync18(framesDir, { recursive: true });
|
|
19690
19916
|
capturePerfs.length = 0;
|
|
19691
19917
|
captureAttempts.length = 0;
|
|
@@ -19728,8 +19954,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19728
19954
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
19729
19955
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
19730
19956
|
});
|
|
19731
|
-
|
|
19732
|
-
|
|
19957
|
+
rmSync18(videoOnlyPath, { force: true });
|
|
19958
|
+
rmSync18(audioScratchPath, { force: true });
|
|
19733
19959
|
}
|
|
19734
19960
|
encodeStageMs = Date.now() - encodeStarted;
|
|
19735
19961
|
} finally {
|
|
@@ -19777,8 +20003,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19777
20003
|
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
19778
20004
|
`, "utf-8");
|
|
19779
20005
|
try {
|
|
19780
|
-
|
|
19781
|
-
|
|
20006
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20007
|
+
rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19782
20008
|
} catch (err) {
|
|
19783
20009
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
19784
20010
|
workDir,
|
|
@@ -19807,18 +20033,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19807
20033
|
|
|
19808
20034
|
// src/services/distributed/planV2Execution.ts
|
|
19809
20035
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
19810
|
-
const workRoot = mkdtempSync9(
|
|
19811
|
-
const materializedPlanDir =
|
|
20036
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
20037
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19812
20038
|
try {
|
|
19813
20039
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
19814
20040
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
19815
20041
|
} finally {
|
|
19816
|
-
|
|
20042
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19817
20043
|
}
|
|
19818
20044
|
}
|
|
19819
20045
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
19820
|
-
const workRoot = mkdtempSync9(
|
|
19821
|
-
const materializedPlanDir =
|
|
20046
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
20047
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19822
20048
|
try {
|
|
19823
20049
|
const materialized = materializePlanV2Target(
|
|
19824
20050
|
planV2Dir,
|
|
@@ -19833,7 +20059,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
19833
20059
|
options
|
|
19834
20060
|
);
|
|
19835
20061
|
} finally {
|
|
19836
|
-
|
|
20062
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19837
20063
|
}
|
|
19838
20064
|
}
|
|
19839
20065
|
|