@hyperframes/producer 0.8.197 → 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/README.md +1 -1
- package/dist/distributed.js +232 -150
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +59 -59
- package/dist/index.js +748 -431
- package/dist/public-server.js +495 -268
- package/dist/renderRequest.d.ts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/services/distributed/chunkWeights.d.ts +16 -0
- package/dist/services/distributed/plan.d.ts +7 -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,
|
|
@@ -14571,9 +14789,73 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14571
14789
|
}
|
|
14572
14790
|
}
|
|
14573
14791
|
|
|
14792
|
+
// src/services/distributed/chunkWeights.ts
|
|
14793
|
+
import { resolveFrameFormat } from "@hyperframes/engine";
|
|
14794
|
+
var PAGE_BASE_WEIGHT = 0.7;
|
|
14795
|
+
var PNG_WEIGHT_FACTOR = 1.5;
|
|
14796
|
+
function isUniformFrameCost(weights) {
|
|
14797
|
+
let lightest = Infinity;
|
|
14798
|
+
let heaviest = 0;
|
|
14799
|
+
for (const weight of weights) {
|
|
14800
|
+
lightest = Math.min(lightest, weight);
|
|
14801
|
+
heaviest = Math.max(heaviest, weight);
|
|
14802
|
+
}
|
|
14803
|
+
return heaviest <= lightest * 1.05;
|
|
14804
|
+
}
|
|
14805
|
+
function planFrameWeights(input) {
|
|
14806
|
+
const { totalFrames, fps } = input;
|
|
14807
|
+
const delta = new Float64Array(totalFrames + 1);
|
|
14808
|
+
const outputPixels = input.width * input.height;
|
|
14809
|
+
const extractedById = new Map(input.extracted.map((entry) => [entry.videoId, entry]));
|
|
14810
|
+
for (const video of input.videos) {
|
|
14811
|
+
const entry = extractedById.get(video.id);
|
|
14812
|
+
if (!entry || video.hidden) continue;
|
|
14813
|
+
const start = Math.max(0, Math.round(video.start * fps));
|
|
14814
|
+
const end = Math.min(totalFrames, Math.round(video.end * fps));
|
|
14815
|
+
if (!(end > start)) continue;
|
|
14816
|
+
const { width, height } = entry.metadata;
|
|
14817
|
+
const factor = resolveFrameFormat(entry.metadata) === "png" ? PNG_WEIGHT_FACTOR : 1;
|
|
14818
|
+
const weight = Math.min(width * height, outputPixels) / 1e6 * factor;
|
|
14819
|
+
delta[start] = delta[start] + weight;
|
|
14820
|
+
delta[end] = delta[end] - weight;
|
|
14821
|
+
}
|
|
14822
|
+
const weights = new Float64Array(totalFrames);
|
|
14823
|
+
let active = 0;
|
|
14824
|
+
for (let frame = 0; frame < totalFrames; frame++) {
|
|
14825
|
+
active += delta[frame];
|
|
14826
|
+
weights[frame] = PAGE_BASE_WEIGHT + active;
|
|
14827
|
+
}
|
|
14828
|
+
return weights;
|
|
14829
|
+
}
|
|
14830
|
+
function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
14831
|
+
if (isUniformFrameCost(weights)) return null;
|
|
14832
|
+
const totalFrames = weights.length;
|
|
14833
|
+
const total = weights.reduce((sum2, weight) => sum2 + weight, 0);
|
|
14834
|
+
const minSize = Math.max(1, Math.min(minFrames, Math.floor(totalFrames / chunkCount)));
|
|
14835
|
+
const maxSize = Math.max(maxFrames, Math.ceil(totalFrames / chunkCount));
|
|
14836
|
+
const slices = [];
|
|
14837
|
+
let startFrame = 0;
|
|
14838
|
+
let frame = 0;
|
|
14839
|
+
let costBefore = 0;
|
|
14840
|
+
for (let index = 0; index < chunkCount - 1; index++) {
|
|
14841
|
+
const target = (index + 1) * total / chunkCount;
|
|
14842
|
+
while (frame < totalFrames && costBefore + weights[frame] / 2 < target) {
|
|
14843
|
+
costBefore += weights[frame++];
|
|
14844
|
+
}
|
|
14845
|
+
const remaining = chunkCount - index - 1;
|
|
14846
|
+
const low = Math.max(startFrame + minSize, totalFrames - remaining * maxSize);
|
|
14847
|
+
const high = Math.min(startFrame + maxSize, totalFrames - remaining * minSize);
|
|
14848
|
+
const endFrame = Math.min(high, Math.max(low, frame));
|
|
14849
|
+
slices.push({ index, startFrame, endFrame });
|
|
14850
|
+
startFrame = endFrame;
|
|
14851
|
+
}
|
|
14852
|
+
slices.push({ index: chunkCount - 1, startFrame, endFrame: totalFrames });
|
|
14853
|
+
return slices;
|
|
14854
|
+
}
|
|
14855
|
+
|
|
14574
14856
|
// src/services/render/stages/freezePlan.ts
|
|
14575
14857
|
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14576
|
-
import { join as
|
|
14858
|
+
import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
|
|
14577
14859
|
|
|
14578
14860
|
// src/services/distributed/planProtocol.ts
|
|
14579
14861
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -14672,7 +14954,7 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
14672
14954
|
|
|
14673
14955
|
// src/services/render/stages/planHash.ts
|
|
14674
14956
|
import { createHash as createHash6 } from "node:crypto";
|
|
14675
|
-
import { closeSync as
|
|
14957
|
+
import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
|
|
14676
14958
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
14677
14959
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
14678
14960
|
function computePlanHash(input) {
|
|
@@ -14738,7 +15020,7 @@ function sha256File(path) {
|
|
|
14738
15020
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
14739
15021
|
} while (bytesRead > 0);
|
|
14740
15022
|
} finally {
|
|
14741
|
-
|
|
15023
|
+
closeSync4(fd);
|
|
14742
15024
|
}
|
|
14743
15025
|
return hash.digest("hex");
|
|
14744
15026
|
}
|
|
@@ -14791,7 +15073,7 @@ function listPlanFiles(planDir) {
|
|
|
14791
15073
|
function walk(dir) {
|
|
14792
15074
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14793
15075
|
for (const entry of entries) {
|
|
14794
|
-
const full =
|
|
15076
|
+
const full = join30(dir, entry.name);
|
|
14795
15077
|
if (entry.isDirectory()) {
|
|
14796
15078
|
walk(full);
|
|
14797
15079
|
} else if (entry.isFile()) {
|
|
@@ -14832,15 +15114,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
14832
15114
|
function writePlanRemoteFiles(planDir, files) {
|
|
14833
15115
|
if (files.length === 0) return;
|
|
14834
15116
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
14835
|
-
writeFileSync6(
|
|
15117
|
+
writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
14836
15118
|
}
|
|
14837
15119
|
function readPlanRemoteFiles(planDir) {
|
|
14838
|
-
const path =
|
|
15120
|
+
const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
14839
15121
|
return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
14840
15122
|
}
|
|
14841
15123
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
14842
|
-
const planJsonPath =
|
|
14843
|
-
const encoderJsonPath =
|
|
15124
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
15125
|
+
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
14844
15126
|
if (!existsSync19(planJsonPath)) {
|
|
14845
15127
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
14846
15128
|
}
|
|
@@ -14877,18 +15159,18 @@ async function freezePlan(input) {
|
|
|
14877
15159
|
if (!existsSync19(planDir)) {
|
|
14878
15160
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
14879
15161
|
}
|
|
14880
|
-
const metaDir =
|
|
15162
|
+
const metaDir = join30(planDir, "meta");
|
|
14881
15163
|
if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
|
|
14882
15164
|
writeFileSync6(
|
|
14883
|
-
|
|
15165
|
+
join30(metaDir, "composition.json"),
|
|
14884
15166
|
`${JSON.stringify(composition, null, 2)}
|
|
14885
15167
|
`,
|
|
14886
15168
|
"utf-8"
|
|
14887
15169
|
);
|
|
14888
15170
|
const encoderForCanonical = stripUndefined(encoder);
|
|
14889
15171
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
14890
|
-
writeFileSync6(
|
|
14891
|
-
writeFileSync6(
|
|
15172
|
+
writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
15173
|
+
writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
14892
15174
|
`, "utf-8");
|
|
14893
15175
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
14894
15176
|
const planHash = computePlanHash({
|
|
@@ -14912,7 +15194,7 @@ async function freezePlan(input) {
|
|
|
14912
15194
|
duration: durationSeconds,
|
|
14913
15195
|
hasAudio
|
|
14914
15196
|
};
|
|
14915
|
-
const planJsonPath =
|
|
15197
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
14916
15198
|
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
14917
15199
|
`, "utf-8");
|
|
14918
15200
|
return { planJsonPath, planHash };
|
|
@@ -14954,16 +15236,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
14954
15236
|
}
|
|
14955
15237
|
|
|
14956
15238
|
// src/services/distributed/chunkAudio.ts
|
|
14957
|
-
import { closeSync as
|
|
15239
|
+
import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
14958
15240
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
14959
|
-
import { join as
|
|
15241
|
+
import { join as join32 } from "node:path";
|
|
14960
15242
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
14961
15243
|
|
|
14962
15244
|
// src/services/distributed/mp4Stitch.ts
|
|
14963
|
-
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";
|
|
14964
15246
|
import { open } from "node:fs/promises";
|
|
14965
15247
|
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
14966
|
-
import { dirname as dirname12, join as
|
|
15248
|
+
import { dirname as dirname12, join as join31 } from "node:path";
|
|
14967
15249
|
import { finished, pipeline } from "node:stream/promises";
|
|
14968
15250
|
function boxAt(src, off, at, limit) {
|
|
14969
15251
|
let size = src.readUInt32BE(off);
|
|
@@ -15329,16 +15611,16 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15329
15611
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15330
15612
|
mkdirSync11(dirname12(outputPath), { recursive: true });
|
|
15331
15613
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15332
|
-
const out = createWriteStream(
|
|
15614
|
+
const out = createWriteStream(join31(workDir, "out.mp4"));
|
|
15333
15615
|
try {
|
|
15334
15616
|
out.write(header);
|
|
15335
15617
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15336
15618
|
out.end();
|
|
15337
15619
|
await finished(out);
|
|
15338
|
-
renameWithRetry5(
|
|
15620
|
+
renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
|
|
15339
15621
|
} finally {
|
|
15340
15622
|
out.destroy();
|
|
15341
|
-
|
|
15623
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
15342
15624
|
}
|
|
15343
15625
|
}
|
|
15344
15626
|
|
|
@@ -15429,7 +15711,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
15429
15711
|
});
|
|
15430
15712
|
const bytes = readFileSync11(input.audioPath);
|
|
15431
15713
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
15432
|
-
mkdirSync12(
|
|
15714
|
+
mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
|
|
15433
15715
|
input.chunks.forEach((_, index) => {
|
|
15434
15716
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
15435
15717
|
if (!cut) return;
|
|
@@ -15437,17 +15719,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
15437
15719
|
const start = offsets[cut.firstPacket];
|
|
15438
15720
|
layout.partStarts[index] = start;
|
|
15439
15721
|
const end = offsets[last] + track.sizes[last];
|
|
15440
|
-
writeFileSync7(
|
|
15722
|
+
writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
15441
15723
|
});
|
|
15442
15724
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
15443
|
-
writeFileSync7(
|
|
15444
|
-
writeFileSync7(
|
|
15725
|
+
writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
15726
|
+
writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
15445
15727
|
}
|
|
15446
15728
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
15447
|
-
const layoutPath =
|
|
15729
|
+
const layoutPath = join32(planDir, SLICE_LAYOUT);
|
|
15448
15730
|
if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
15449
15731
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
15450
|
-
const frame = readFileSync11(
|
|
15732
|
+
const frame = readFileSync11(join32(planDir, SLICE_FRAME));
|
|
15451
15733
|
const fd = openSync5(scratchPath, "w");
|
|
15452
15734
|
try {
|
|
15453
15735
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -15460,12 +15742,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
15460
15742
|
);
|
|
15461
15743
|
const start = layout.partStarts[chunkIndex];
|
|
15462
15744
|
if (start !== void 0) {
|
|
15463
|
-
const part = readFileSync11(
|
|
15745
|
+
const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
15464
15746
|
writeSync(fd, part, 0, part.length, start);
|
|
15465
15747
|
}
|
|
15466
15748
|
ftruncateSync(fd, layout.sizeBytes);
|
|
15467
15749
|
} finally {
|
|
15468
|
-
|
|
15750
|
+
closeSync5(fd);
|
|
15469
15751
|
}
|
|
15470
15752
|
return scratchPath;
|
|
15471
15753
|
}
|
|
@@ -15516,7 +15798,7 @@ async function muxChunkWithAudio(input) {
|
|
|
15516
15798
|
// src/services/distributed/planSize.ts
|
|
15517
15799
|
import { createHash as createHash7 } from "node:crypto";
|
|
15518
15800
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
15519
|
-
import { extname as extname5, join as
|
|
15801
|
+
import { extname as extname5, join as join33, relative as relative4 } from "node:path";
|
|
15520
15802
|
var MAX_TOP_COMPONENTS = 10;
|
|
15521
15803
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
15522
15804
|
".aac",
|
|
@@ -15628,7 +15910,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
15628
15910
|
return;
|
|
15629
15911
|
}
|
|
15630
15912
|
for (const entry of entries) {
|
|
15631
|
-
const full =
|
|
15913
|
+
const full = join33(dir, entry.name);
|
|
15632
15914
|
if (entry.isDirectory()) {
|
|
15633
15915
|
walkRegularFiles(full, visit);
|
|
15634
15916
|
} else if (entry.isFile()) {
|
|
@@ -15685,6 +15967,7 @@ var PLAN_PROJECT_DIR_SKIP_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
15685
15967
|
]);
|
|
15686
15968
|
var DEFAULT_MAX_PARALLEL_CHUNKS = 16;
|
|
15687
15969
|
var MIN_CHUNK_SIZE = 10;
|
|
15970
|
+
var MAX_CHUNK_SIZE = 3600;
|
|
15688
15971
|
var MAX_PARALLEL_CHUNKS_CEILING = 256;
|
|
15689
15972
|
var ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME = 0.11;
|
|
15690
15973
|
var ESTIMATED_CHUNK_OVERHEAD_SECONDS = 60;
|
|
@@ -15711,6 +15994,11 @@ var ChunkExceedsWorkerTimeoutError = class extends Error {
|
|
|
15711
15994
|
this.name = "ChunkExceedsWorkerTimeoutError";
|
|
15712
15995
|
}
|
|
15713
15996
|
};
|
|
15997
|
+
function framesWithinWorkerTimeout(workerTimeoutSeconds, secondsPerFrame) {
|
|
15998
|
+
return Math.floor(
|
|
15999
|
+
(workerTimeoutSeconds * WORKER_TIMEOUT_MARGIN - ESTIMATED_CHUNK_OVERHEAD_SECONDS) / secondsPerFrame
|
|
16000
|
+
);
|
|
16001
|
+
}
|
|
15714
16002
|
function resolveMaxParallelChunks(config, totalFrames, megapixels, log = defaultLogger, measuredSecondsPerFrame) {
|
|
15715
16003
|
if (config.maxParallelChunks !== void 0) return config.maxParallelChunks;
|
|
15716
16004
|
const { workerTimeoutSeconds, workerConcurrency } = config;
|
|
@@ -15723,9 +16011,7 @@ function resolveMaxParallelChunks(config, totalFrames, megapixels, log = default
|
|
|
15723
16011
|
const minFramesPerChunk = Math.ceil(ESTIMATED_CHUNK_OVERHEAD_SECONDS / secondsPerFrame);
|
|
15724
16012
|
const wide = Math.max(1, Math.min(wave, Math.floor(totalFrames / minFramesPerChunk)));
|
|
15725
16013
|
if (workerTimeoutSeconds === void 0) return wide;
|
|
15726
|
-
const maxFramesPerChunk =
|
|
15727
|
-
(workerTimeoutSeconds * WORKER_TIMEOUT_MARGIN - ESTIMATED_CHUNK_OVERHEAD_SECONDS) / secondsPerFrame
|
|
15728
|
-
);
|
|
16014
|
+
const maxFramesPerChunk = framesWithinWorkerTimeout(workerTimeoutSeconds, secondsPerFrame);
|
|
15729
16015
|
const needed = maxFramesPerChunk < 1 ? Infinity : Math.ceil(totalFrames / maxFramesPerChunk);
|
|
15730
16016
|
if (needed > MAX_PARALLEL_CHUNKS_CEILING) {
|
|
15731
16017
|
const framesAtCeiling = Math.ceil(totalFrames / MAX_PARALLEL_CHUNKS_CEILING);
|
|
@@ -15836,13 +16122,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
15836
16122
|
}
|
|
15837
16123
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
15838
16124
|
"plan.json",
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
16125
|
+
join34("meta", "composition.json"),
|
|
16126
|
+
join34("meta", "encoder.json"),
|
|
16127
|
+
join34("meta", "chunks.json")
|
|
15842
16128
|
];
|
|
15843
16129
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
15844
16130
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
15845
|
-
|
|
16131
|
+
rmSync13(join34(planDir, relativePath), { force: true });
|
|
15846
16132
|
}
|
|
15847
16133
|
}
|
|
15848
16134
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -15854,6 +16140,15 @@ function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
|
15854
16140
|
}
|
|
15855
16141
|
return slices;
|
|
15856
16142
|
}
|
|
16143
|
+
function buildPlanChunkSlices(frameWeights, config, chunkCount, effectiveChunkSize, maxFramesPerChunk = Infinity) {
|
|
16144
|
+
const weighted = config.chunkSize === void 0 ? buildWeightedChunkSlices(
|
|
16145
|
+
frameWeights,
|
|
16146
|
+
chunkCount,
|
|
16147
|
+
MIN_CHUNK_SIZE,
|
|
16148
|
+
Math.min(config.targetChunkFrames ?? MAX_CHUNK_SIZE, MAX_CHUNK_SIZE, maxFramesPerChunk)
|
|
16149
|
+
) : null;
|
|
16150
|
+
return weighted ?? buildChunkSlices(frameWeights.length, chunkCount, effectiveChunkSize);
|
|
16151
|
+
}
|
|
15857
16152
|
async function readFontSnapshotSha() {
|
|
15858
16153
|
const module = await import("@hyperframes/core/fonts/embedded-data");
|
|
15859
16154
|
const data = module.EMBEDDED_FONT_DATA;
|
|
@@ -15975,19 +16270,19 @@ function createVideoSourceShipper(input) {
|
|
|
15975
16270
|
const known = shipped.get(srcPath);
|
|
15976
16271
|
if (known) return known;
|
|
15977
16272
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
15978
|
-
const current = stagedRel === null ? srcPath :
|
|
16273
|
+
const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
|
|
15979
16274
|
let result = null;
|
|
15980
16275
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
15981
16276
|
const rel = pathInside(root, srcPath);
|
|
15982
|
-
if (rel !== null && (existsSync21(
|
|
16277
|
+
if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
15983
16278
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
15984
16279
|
break;
|
|
15985
16280
|
}
|
|
15986
16281
|
}
|
|
15987
16282
|
if (result === null) {
|
|
15988
16283
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
15989
|
-
const destination =
|
|
15990
|
-
mkdirSync13(
|
|
16284
|
+
const destination = join34(input.planDir, ...result.split("/"));
|
|
16285
|
+
mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
|
|
15991
16286
|
try {
|
|
15992
16287
|
linkSync(current, destination);
|
|
15993
16288
|
} catch {
|
|
@@ -16036,14 +16331,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16036
16331
|
variables: config.variables
|
|
16037
16332
|
});
|
|
16038
16333
|
const entryFile = config.entryFile ?? "index.html";
|
|
16039
|
-
const htmlPath =
|
|
16334
|
+
const htmlPath = join34(projectDir, entryFile);
|
|
16040
16335
|
if (!existsSync21(htmlPath)) {
|
|
16041
16336
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16042
16337
|
}
|
|
16043
|
-
const workDir =
|
|
16044
|
-
|
|
16338
|
+
const workDir = join34(planDir, ".plan-work");
|
|
16339
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16045
16340
|
mkdirSync13(workDir, { recursive: true });
|
|
16046
|
-
const compiledDir =
|
|
16341
|
+
const compiledDir = join34(workDir, "compiled");
|
|
16047
16342
|
mkdirSync13(compiledDir, { recursive: true });
|
|
16048
16343
|
let stageStarted = Date.now();
|
|
16049
16344
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -16057,10 +16352,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16057
16352
|
}
|
|
16058
16353
|
});
|
|
16059
16354
|
logPlanStage(log, "copy", stageStarted);
|
|
16060
|
-
const finalCompiledDir =
|
|
16355
|
+
const finalCompiledDir = join34(planDir, "compiled");
|
|
16061
16356
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
16062
16357
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
16063
|
-
const remoteLocalPath = (rel) =>
|
|
16358
|
+
const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
|
|
16064
16359
|
for (const [, file] of remoteFiles) {
|
|
16065
16360
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
16066
16361
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -16246,17 +16541,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16246
16541
|
let stageStarted = Date.now();
|
|
16247
16542
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
16248
16543
|
logPlanStage(log, "extract", stageStarted);
|
|
16249
|
-
const stagedVideoFrames =
|
|
16250
|
-
const videoFramesDst =
|
|
16251
|
-
if (existsSync21(videoFramesDst))
|
|
16252
|
-
const videoSourcesDir =
|
|
16253
|
-
|
|
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 });
|
|
16254
16549
|
if (existsSync21(stagedVideoFrames)) {
|
|
16255
16550
|
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
16256
16551
|
} else {
|
|
16257
16552
|
mkdirSync13(videoFramesDst, { recursive: true });
|
|
16258
16553
|
}
|
|
16259
|
-
if (existsSync21(finalCompiledDir))
|
|
16554
|
+
if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
|
|
16260
16555
|
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
16261
16556
|
const shipVideoSource = createVideoSourceShipper({
|
|
16262
16557
|
planDir,
|
|
@@ -16286,7 +16581,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16286
16581
|
};
|
|
16287
16582
|
})
|
|
16288
16583
|
});
|
|
16289
|
-
mkdirSync13(
|
|
16584
|
+
mkdirSync13(join34(planDir, "meta"), { recursive: true });
|
|
16290
16585
|
writePlanRemoteFiles(
|
|
16291
16586
|
planDir,
|
|
16292
16587
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -16296,13 +16591,21 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16296
16591
|
}))
|
|
16297
16592
|
);
|
|
16298
16593
|
writeFileSync8(
|
|
16299
|
-
|
|
16594
|
+
join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
16300
16595
|
JSON.stringify(planVideosJson, null, 2),
|
|
16301
16596
|
"utf-8"
|
|
16302
16597
|
);
|
|
16303
16598
|
const digests = options.digests ?? /* @__PURE__ */ new Map();
|
|
16304
16599
|
const separateAudio = plansSeparateAudio(config, options);
|
|
16305
16600
|
const chunksCarryAudio = !separateAudio && carriesChunkAudio(config, options);
|
|
16601
|
+
const frameWeights = planFrameWeights({
|
|
16602
|
+
videos: planVideosJson.videos,
|
|
16603
|
+
extracted: planVideosJson.extracted,
|
|
16604
|
+
totalFrames,
|
|
16605
|
+
fps: config.fps,
|
|
16606
|
+
width,
|
|
16607
|
+
height
|
|
16608
|
+
});
|
|
16306
16609
|
stageStarted = Date.now();
|
|
16307
16610
|
const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
|
|
16308
16611
|
separateAudio ? null : mixPlanAudio(c, finalCompiledDir).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
@@ -16353,12 +16656,21 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16353
16656
|
maxParallel,
|
|
16354
16657
|
config.targetChunkFrames
|
|
16355
16658
|
);
|
|
16356
|
-
const chunks =
|
|
16659
|
+
const chunks = buildPlanChunkSlices(
|
|
16660
|
+
frameWeights,
|
|
16661
|
+
config,
|
|
16662
|
+
chunkCount,
|
|
16663
|
+
effectiveChunkSize,
|
|
16664
|
+
chunkSizingUsesFrameCost(config) && config.workerTimeoutSeconds !== void 0 ? framesWithinWorkerTimeout(
|
|
16665
|
+
config.workerTimeoutSeconds,
|
|
16666
|
+
estimateFrameSeconds(megapixels, frameCostOutcome.value)
|
|
16667
|
+
) : void 0
|
|
16668
|
+
);
|
|
16357
16669
|
const audioResult = audioOutcome.value;
|
|
16358
16670
|
if (audioResult?.audioError) {
|
|
16359
16671
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
16360
16672
|
}
|
|
16361
|
-
const planAudioPath =
|
|
16673
|
+
const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16362
16674
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16363
16675
|
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
16364
16676
|
}
|
|
@@ -16407,7 +16719,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16407
16719
|
format: config.format
|
|
16408
16720
|
};
|
|
16409
16721
|
try {
|
|
16410
|
-
|
|
16722
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
16411
16723
|
} catch (err) {
|
|
16412
16724
|
log.warn("[plan] failed to remove temp work dir", {
|
|
16413
16725
|
workDir,
|
|
@@ -16499,7 +16811,6 @@ var ALLOWED_RUNTIME_CAPS = ["lambda", "temporal", "cloud-run-job", "k8s-job", "n
|
|
|
16499
16811
|
var ALLOWED_HDR_MODES = ["auto", "force-sdr"];
|
|
16500
16812
|
var MAX_DIMENSION = 7680;
|
|
16501
16813
|
var MIN_DIMENSION = 16;
|
|
16502
|
-
var MAX_CHUNK_SIZE = 3600;
|
|
16503
16814
|
function validateDistributedRenderConfig(config) {
|
|
16504
16815
|
if (config === null || typeof config !== "object") {
|
|
16505
16816
|
throw new InvalidConfigError("config", "must be an object");
|
|
@@ -16783,6 +17094,7 @@ function assertRequestOptionScalars(options) {
|
|
|
16783
17094
|
assertOptionalInteger(options, "gifLoop");
|
|
16784
17095
|
assertOptionalInteger(options, "hlsSegmentSeconds", 1);
|
|
16785
17096
|
assertOptionalInteger(options, "workers", 1);
|
|
17097
|
+
assertOptionalInteger(options, "maxRenderMs");
|
|
16786
17098
|
assertOptionalInteger(options, "crf");
|
|
16787
17099
|
for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
|
|
16788
17100
|
assertOptionalBoolean(options, field);
|
|
@@ -16979,31 +17291,32 @@ import {
|
|
|
16979
17291
|
import {
|
|
16980
17292
|
existsSync as existsSync24,
|
|
16981
17293
|
mkdirSync as mkdirSync14,
|
|
16982
|
-
statSync as
|
|
17294
|
+
statSync as statSync9,
|
|
16983
17295
|
mkdtempSync as mkdtempSync5,
|
|
16984
17296
|
writeFileSync as writeFileSync9,
|
|
16985
|
-
rmSync as
|
|
17297
|
+
rmSync as rmSync14,
|
|
16986
17298
|
createReadStream as createReadStream5
|
|
16987
17299
|
} from "node:fs";
|
|
16988
|
-
import { resolve as resolve10, dirname as dirname14, join as
|
|
17300
|
+
import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
|
|
16989
17301
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
16990
17302
|
import { parseArgs } from "node:util";
|
|
16991
17303
|
import crypto from "node:crypto";
|
|
16992
17304
|
import { Hono as Hono2 } from "hono";
|
|
16993
17305
|
import { streamSSE } from "hono/streaming";
|
|
16994
17306
|
import { serve as serve2 } from "@hono/node-server";
|
|
17307
|
+
import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engine";
|
|
16995
17308
|
|
|
16996
17309
|
// src/services/hyperframeLint.ts
|
|
16997
17310
|
import {
|
|
16998
17311
|
existsSync as existsSync22,
|
|
16999
17312
|
readFileSync as readFileSync12,
|
|
17000
|
-
statSync as
|
|
17313
|
+
statSync as statSync8,
|
|
17001
17314
|
openSync as openSync6,
|
|
17002
|
-
fstatSync as
|
|
17003
|
-
closeSync as
|
|
17315
|
+
fstatSync as fstatSync3,
|
|
17316
|
+
closeSync as closeSync6,
|
|
17004
17317
|
constants as constants3
|
|
17005
17318
|
} from "node:fs";
|
|
17006
|
-
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";
|
|
17007
17320
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
17008
17321
|
function isStringRecord(value) {
|
|
17009
17322
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -17037,19 +17350,19 @@ function readEntryHtml(filePath) {
|
|
|
17037
17350
|
} catch (error) {
|
|
17038
17351
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
17039
17352
|
return null;
|
|
17040
|
-
if (!
|
|
17353
|
+
if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
17041
17354
|
throw error;
|
|
17042
17355
|
}
|
|
17043
17356
|
try {
|
|
17044
|
-
if (!
|
|
17357
|
+
if (!fstatSync3(fd).isFile()) return null;
|
|
17045
17358
|
return readFileSync12(fd, "utf-8");
|
|
17046
17359
|
} finally {
|
|
17047
|
-
|
|
17360
|
+
closeSync6(fd);
|
|
17048
17361
|
}
|
|
17049
17362
|
}
|
|
17050
17363
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
17051
17364
|
const absProjectDir = resolve9(projectDir);
|
|
17052
|
-
if (!existsSync22(absProjectDir) || !
|
|
17365
|
+
if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
17053
17366
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17054
17367
|
}
|
|
17055
17368
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -17071,7 +17384,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
17071
17384
|
}
|
|
17072
17385
|
}
|
|
17073
17386
|
return {
|
|
17074
|
-
error: `No HTML entry file found in project directory: ${
|
|
17387
|
+
error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
|
|
17075
17388
|
};
|
|
17076
17389
|
}
|
|
17077
17390
|
function prepareHyperframeLintBody(body) {
|
|
@@ -17117,7 +17430,7 @@ async function runHyperframeLint(prepared) {
|
|
|
17117
17430
|
// src/services/healthWorker.ts
|
|
17118
17431
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
17119
17432
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17120
|
-
import { dirname as dirname13, join as
|
|
17433
|
+
import { dirname as dirname13, join as join36 } from "node:path";
|
|
17121
17434
|
import { existsSync as existsSync23 } from "node:fs";
|
|
17122
17435
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
17123
17436
|
async function startHealthWorker(options = {}) {
|
|
@@ -17193,7 +17506,7 @@ function defaultLogger2() {
|
|
|
17193
17506
|
}
|
|
17194
17507
|
function resolveWorkerEntry2() {
|
|
17195
17508
|
const here = dirname13(fileURLToPath6(import.meta.url));
|
|
17196
|
-
const candidates = [
|
|
17509
|
+
const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
|
|
17197
17510
|
for (const candidate of candidates) {
|
|
17198
17511
|
if (existsSync23(candidate)) return candidate;
|
|
17199
17512
|
}
|
|
@@ -17214,7 +17527,8 @@ var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
17214
17527
|
"INVALID_VIDEO_METADATA",
|
|
17215
17528
|
"VIDEO_SOURCE_UNRENDERABLE",
|
|
17216
17529
|
"VIDEO_EXTRACTION_FAILED",
|
|
17217
|
-
"ENCODER_INTERRUPTED"
|
|
17530
|
+
"ENCODER_INTERRUPTED",
|
|
17531
|
+
"RENDER_TOO_SLOW"
|
|
17218
17532
|
]);
|
|
17219
17533
|
function extractSafeRenderErrorCode(error) {
|
|
17220
17534
|
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
@@ -17273,6 +17587,7 @@ function parseRenderOptions(body) {
|
|
|
17273
17587
|
const fps = parseServerFps(body.fps);
|
|
17274
17588
|
const quality = parseServerQuality(body.quality);
|
|
17275
17589
|
const workers = typeof body.workers === "number" ? body.workers : void 0;
|
|
17590
|
+
const maxRenderMs = typeof body.maxRenderMs === "number" ? body.maxRenderMs : void 0;
|
|
17276
17591
|
const useGpu = body.gpu === true;
|
|
17277
17592
|
const debug = body.debug === true;
|
|
17278
17593
|
const strictness = body.bestEffort === false ? "strict" : "best-effort";
|
|
@@ -17288,6 +17603,7 @@ function parseRenderOptions(body) {
|
|
|
17288
17603
|
fps,
|
|
17289
17604
|
quality,
|
|
17290
17605
|
workers,
|
|
17606
|
+
maxRenderMs,
|
|
17291
17607
|
crf,
|
|
17292
17608
|
videoBitrate,
|
|
17293
17609
|
useGpu,
|
|
@@ -17321,6 +17637,7 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
17321
17637
|
quality: input.quality,
|
|
17322
17638
|
format: input.format ?? "mp4",
|
|
17323
17639
|
workers: input.workers,
|
|
17640
|
+
maxRenderMs: input.maxRenderMs,
|
|
17324
17641
|
crf: input.crf,
|
|
17325
17642
|
videoBitrate: input.videoBitrate,
|
|
17326
17643
|
useGpu: input.useGpu,
|
|
@@ -17347,6 +17664,9 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
17347
17664
|
function validateRenderOverrides(body) {
|
|
17348
17665
|
const encoder = parseEncoderOptions(body, parseServerFormat(body.format) ?? "mp4");
|
|
17349
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
|
+
}
|
|
17350
17670
|
if (body.variables !== void 0 && !isPlainObject2(body.variables)) {
|
|
17351
17671
|
return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
|
|
17352
17672
|
}
|
|
@@ -17374,11 +17694,12 @@ function validateOutputResolutionOverride(body) {
|
|
|
17374
17694
|
}
|
|
17375
17695
|
return void 0;
|
|
17376
17696
|
}
|
|
17697
|
+
var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
|
|
17377
17698
|
function prepareProjectDirectory(projectDir, options) {
|
|
17378
17699
|
const candidate = nonEmptyString(projectDir);
|
|
17379
17700
|
if (!candidate) return null;
|
|
17380
17701
|
const absProjectDir = resolve10(candidate);
|
|
17381
|
-
if (!existsSync24(absProjectDir) || !
|
|
17702
|
+
if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
17382
17703
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17383
17704
|
}
|
|
17384
17705
|
const entry = options.entryFile || "index.html";
|
|
@@ -17394,11 +17715,7 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17394
17715
|
if (!previewUrl)
|
|
17395
17716
|
return { error: "Missing render source: provide projectDir, previewUrl, or html" };
|
|
17396
17717
|
try {
|
|
17397
|
-
|
|
17398
|
-
if (!response.ok) {
|
|
17399
|
-
return { error: `Failed to fetch previewUrl: ${response.status} ${response.statusText}` };
|
|
17400
|
-
}
|
|
17401
|
-
return { html: await response.text() };
|
|
17718
|
+
return { html: await fetchPublicHttpsText2(previewUrl, { maxBytes: MAX_PREVIEW_HTML_BYTES }) };
|
|
17402
17719
|
} catch (error) {
|
|
17403
17720
|
return {
|
|
17404
17721
|
error: `Failed to fetch previewUrl: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -17407,8 +17724,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17407
17724
|
}
|
|
17408
17725
|
function materializeInlineProject(html, options) {
|
|
17409
17726
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
17410
|
-
const tempProjectDir = mkdtempSync5(
|
|
17411
|
-
writeFileSync9(
|
|
17727
|
+
const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
|
|
17728
|
+
writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
|
|
17412
17729
|
return {
|
|
17413
17730
|
prepared: {
|
|
17414
17731
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -17469,7 +17786,7 @@ function createArtifactStore(ttlMs) {
|
|
|
17469
17786
|
function cleanupTempDir(dir, log) {
|
|
17470
17787
|
if (!dir) return;
|
|
17471
17788
|
try {
|
|
17472
|
-
|
|
17789
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
17473
17790
|
} catch (error) {
|
|
17474
17791
|
log.warn("Failed to cleanup temp project dir", {
|
|
17475
17792
|
cleanupProjectDir: dir,
|
|
@@ -17478,7 +17795,7 @@ function cleanupTempDir(dir, log) {
|
|
|
17478
17795
|
}
|
|
17479
17796
|
}
|
|
17480
17797
|
function outputFileSize(path) {
|
|
17481
|
-
return existsSync24(path) ?
|
|
17798
|
+
return existsSync24(path) ? statSync9(path).size : 0;
|
|
17482
17799
|
}
|
|
17483
17800
|
function createBlockingProgressReporter(log, requestId) {
|
|
17484
17801
|
let lastLoggedPct = -10;
|
|
@@ -17778,7 +18095,7 @@ function createRenderHandlers(options = {}) {
|
|
|
17778
18095
|
store.delete(token);
|
|
17779
18096
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
17780
18097
|
}
|
|
17781
|
-
const stats =
|
|
18098
|
+
const stats = statSync9(artifact.path);
|
|
17782
18099
|
return new Response(createReadStream5(artifact.path), {
|
|
17783
18100
|
headers: {
|
|
17784
18101
|
"content-type": "video/mp4",
|
|
@@ -17877,12 +18194,12 @@ import {
|
|
|
17877
18194
|
mkdtempSync as mkdtempSync7,
|
|
17878
18195
|
readFileSync as readFileSync13,
|
|
17879
18196
|
readdirSync as readdirSync9,
|
|
17880
|
-
rmSync as
|
|
17881
|
-
statSync as
|
|
18197
|
+
rmSync as rmSync16,
|
|
18198
|
+
statSync as statSync11,
|
|
17882
18199
|
writeFileSync as writeFileSync11
|
|
17883
18200
|
} from "node:fs";
|
|
17884
18201
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
17885
|
-
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";
|
|
17886
18203
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
17887
18204
|
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
17888
18205
|
|
|
@@ -17893,12 +18210,12 @@ import {
|
|
|
17893
18210
|
linkSync as linkSync2,
|
|
17894
18211
|
mkdirSync as mkdirSync15,
|
|
17895
18212
|
mkdtempSync as mkdtempSync6,
|
|
17896
|
-
rmSync as
|
|
17897
|
-
statSync as
|
|
18213
|
+
rmSync as rmSync15,
|
|
18214
|
+
statSync as statSync10,
|
|
17898
18215
|
writeFileSync as writeFileSync10
|
|
17899
18216
|
} from "node:fs";
|
|
17900
18217
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
17901
|
-
import { dirname as dirname15, join as
|
|
18218
|
+
import { dirname as dirname15, join as join39 } from "node:path";
|
|
17902
18219
|
|
|
17903
18220
|
// src/services/distributed/planV2Errors.ts
|
|
17904
18221
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -17913,7 +18230,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
17913
18230
|
};
|
|
17914
18231
|
|
|
17915
18232
|
// src/services/distributed/planV2Layout.ts
|
|
17916
|
-
import { join as
|
|
18233
|
+
import { join as join38 } from "node:path";
|
|
17917
18234
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
17918
18235
|
function assertPlanV2Sha256(value, field) {
|
|
17919
18236
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -17923,7 +18240,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
17923
18240
|
}
|
|
17924
18241
|
function planV2BlobPath(root, sha256) {
|
|
17925
18242
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
17926
|
-
return
|
|
18243
|
+
return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
17927
18244
|
}
|
|
17928
18245
|
|
|
17929
18246
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -17964,11 +18281,11 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17964
18281
|
this.destinationDir = destinationDir;
|
|
17965
18282
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
17966
18283
|
mkdirSync15(dirname15(destinationDir), { recursive: true });
|
|
17967
|
-
this.temporaryDir = mkdtempSync6(
|
|
18284
|
+
this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
|
|
17968
18285
|
}
|
|
17969
18286
|
async putBlob(blob) {
|
|
17970
18287
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
17971
|
-
const sourceSize =
|
|
18288
|
+
const sourceSize = statSync10(blob.sourcePath).size;
|
|
17972
18289
|
if (sourceSize !== blob.sizeBytes) {
|
|
17973
18290
|
throw new PlanV2IntegrityError(
|
|
17974
18291
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -17977,8 +18294,8 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17977
18294
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
17978
18295
|
if (existsSync25(destinationPath)) return;
|
|
17979
18296
|
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
17980
|
-
const stagingDir = mkdtempSync6(
|
|
17981
|
-
const temporaryPath =
|
|
18297
|
+
const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18298
|
+
const temporaryPath = join39(stagingDir, "blob");
|
|
17982
18299
|
try {
|
|
17983
18300
|
try {
|
|
17984
18301
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -17988,7 +18305,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17988
18305
|
}
|
|
17989
18306
|
renameWithRetry7(temporaryPath, destinationPath);
|
|
17990
18307
|
} finally {
|
|
17991
|
-
|
|
18308
|
+
rmSync15(stagingDir, { recursive: true, force: true });
|
|
17992
18309
|
}
|
|
17993
18310
|
}
|
|
17994
18311
|
async commitManifest(manifestBytes) {
|
|
@@ -17999,13 +18316,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17999
18316
|
);
|
|
18000
18317
|
}
|
|
18001
18318
|
}
|
|
18002
|
-
writeFileSync10(
|
|
18319
|
+
writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18003
18320
|
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
18004
18321
|
this.#committed = true;
|
|
18005
18322
|
}
|
|
18006
18323
|
async abort() {
|
|
18007
18324
|
if (!this.#committed) {
|
|
18008
|
-
|
|
18325
|
+
rmSync15(this.temporaryDir, { recursive: true, force: true });
|
|
18009
18326
|
}
|
|
18010
18327
|
}
|
|
18011
18328
|
};
|
|
@@ -18044,7 +18361,7 @@ function listFiles(root) {
|
|
|
18044
18361
|
const rootResolved = resolve11(root);
|
|
18045
18362
|
function walk(dir) {
|
|
18046
18363
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18047
|
-
const absolutePath =
|
|
18364
|
+
const absolutePath = join40(dir, entry.name);
|
|
18048
18365
|
if (entry.isDirectory()) {
|
|
18049
18366
|
walk(absolutePath);
|
|
18050
18367
|
} else if (entry.isFile()) {
|
|
@@ -18071,13 +18388,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18071
18388
|
}
|
|
18072
18389
|
}
|
|
18073
18390
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18074
|
-
const videoRoot =
|
|
18391
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18075
18392
|
if (!existsSync26(videoRoot)) return;
|
|
18076
18393
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18077
18394
|
if (!videoEntry.isDirectory()) continue;
|
|
18078
|
-
const videoDir =
|
|
18395
|
+
const videoDir = join40(videoRoot, videoEntry.name);
|
|
18079
18396
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18080
|
-
const sentinelPath =
|
|
18397
|
+
const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18081
18398
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18082
18399
|
}
|
|
18083
18400
|
}
|
|
@@ -18124,7 +18441,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18124
18441
|
if (video.deferredRange) {
|
|
18125
18442
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18126
18443
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18127
|
-
framePaths2.set(index,
|
|
18444
|
+
framePaths2.set(index, join40(outputDir, String(index)));
|
|
18128
18445
|
}
|
|
18129
18446
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18130
18447
|
}
|
|
@@ -18132,7 +18449,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18132
18449
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18133
18450
|
for (const frameName of frameNames) {
|
|
18134
18451
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18135
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18452
|
+
assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
|
|
18136
18453
|
continue;
|
|
18137
18454
|
}
|
|
18138
18455
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18151,7 +18468,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18151
18468
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18152
18469
|
);
|
|
18153
18470
|
}
|
|
18154
|
-
framePaths.set(frameIndex,
|
|
18471
|
+
framePaths.set(frameIndex, join40(outputDir, frameName));
|
|
18155
18472
|
}
|
|
18156
18473
|
return {
|
|
18157
18474
|
...video,
|
|
@@ -18172,7 +18489,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18172
18489
|
}
|
|
18173
18490
|
}
|
|
18174
18491
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18175
|
-
const videosPath =
|
|
18492
|
+
const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18176
18493
|
if (!existsSync26(videosPath)) return;
|
|
18177
18494
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18178
18495
|
for (const video of videos.extracted) {
|
|
@@ -18201,13 +18518,13 @@ function parseChunkSlices(value) {
|
|
|
18201
18518
|
});
|
|
18202
18519
|
}
|
|
18203
18520
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18204
|
-
const videoRoot =
|
|
18521
|
+
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18205
18522
|
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18206
|
-
const videosPath =
|
|
18523
|
+
const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18207
18524
|
if (!existsSync26(videosPath)) {
|
|
18208
18525
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18209
18526
|
}
|
|
18210
|
-
const chunksPath =
|
|
18527
|
+
const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
|
|
18211
18528
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18212
18529
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18213
18530
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18263,20 +18580,20 @@ function computeManifestHash(manifest) {
|
|
|
18263
18580
|
function writeBlob(sourcePath, destinationPath) {
|
|
18264
18581
|
if (existsSync26(destinationPath)) return;
|
|
18265
18582
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18266
|
-
const temporaryDir = mkdtempSync7(
|
|
18267
|
-
const temporaryPath =
|
|
18583
|
+
const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18584
|
+
const temporaryPath = join40(temporaryDir, "blob");
|
|
18268
18585
|
try {
|
|
18269
18586
|
copyFileSync7(sourcePath, temporaryPath);
|
|
18270
18587
|
renameWithRetry8(temporaryPath, destinationPath);
|
|
18271
18588
|
} finally {
|
|
18272
|
-
|
|
18589
|
+
rmSync16(temporaryDir, { recursive: true, force: true });
|
|
18273
18590
|
}
|
|
18274
18591
|
}
|
|
18275
18592
|
function publishBlobFor(absolutePath, digests) {
|
|
18276
18593
|
return {
|
|
18277
18594
|
sourcePath: absolutePath,
|
|
18278
18595
|
sha256: planFileSha256(absolutePath, digests),
|
|
18279
|
-
sizeBytes:
|
|
18596
|
+
sizeBytes: statSync11(absolutePath).size
|
|
18280
18597
|
};
|
|
18281
18598
|
}
|
|
18282
18599
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18300,7 +18617,7 @@ async function putTasks(tasks) {
|
|
|
18300
18617
|
if (failure) throw failure.reason;
|
|
18301
18618
|
}
|
|
18302
18619
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18303
|
-
const executionPlanPath =
|
|
18620
|
+
const executionPlanPath = join40(executionPlanDir, "plan.json");
|
|
18304
18621
|
if (!existsSync26(executionPlanPath)) {
|
|
18305
18622
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18306
18623
|
}
|
|
@@ -18328,7 +18645,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18328
18645
|
}
|
|
18329
18646
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18330
18647
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18331
|
-
const encoder = readJsonFile(
|
|
18648
|
+
const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18332
18649
|
const files = listFiles(executionPlanDir);
|
|
18333
18650
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18334
18651
|
for (const file of files) {
|
|
@@ -18389,20 +18706,20 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18389
18706
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18390
18707
|
}
|
|
18391
18708
|
mkdirSync16(dirname16(planV2Dir), { recursive: true });
|
|
18392
|
-
const tempDir = mkdtempSync7(
|
|
18709
|
+
const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
|
|
18393
18710
|
try {
|
|
18394
18711
|
for (const blob of publication.blobs) {
|
|
18395
18712
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18396
18713
|
}
|
|
18397
18714
|
writeFileSync11(
|
|
18398
|
-
|
|
18715
|
+
join40(tempDir, "plan.json"),
|
|
18399
18716
|
canonicalJsonStringify(publication.manifest),
|
|
18400
18717
|
"utf-8"
|
|
18401
18718
|
);
|
|
18402
18719
|
renameWithRetry8(tempDir, planV2Dir);
|
|
18403
18720
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18404
18721
|
} catch (error) {
|
|
18405
|
-
|
|
18722
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18406
18723
|
throw error;
|
|
18407
18724
|
}
|
|
18408
18725
|
}
|
|
@@ -18442,7 +18759,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18442
18759
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18443
18760
|
}
|
|
18444
18761
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18445
|
-
const stagingRoot = mkdtempSync7(
|
|
18762
|
+
const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
18446
18763
|
const digests = /* @__PURE__ */ new Map();
|
|
18447
18764
|
try {
|
|
18448
18765
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18453,7 +18770,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18453
18770
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18454
18771
|
whileAudioMixes: () => putBlobs(
|
|
18455
18772
|
publisher,
|
|
18456
|
-
listFiles(
|
|
18773
|
+
listFiles(join40(stagingRoot, "compiled")).map(
|
|
18457
18774
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18458
18775
|
)
|
|
18459
18776
|
),
|
|
@@ -18469,7 +18786,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18469
18786
|
}
|
|
18470
18787
|
throw error;
|
|
18471
18788
|
} finally {
|
|
18472
|
-
|
|
18789
|
+
rmSync16(stagingRoot, { recursive: true, force: true });
|
|
18473
18790
|
}
|
|
18474
18791
|
}
|
|
18475
18792
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -18485,7 +18802,7 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
18485
18802
|
function resultFromManifest(planV2Dir, manifest) {
|
|
18486
18803
|
return {
|
|
18487
18804
|
planDir: planV2Dir,
|
|
18488
|
-
manifestPath:
|
|
18805
|
+
manifestPath: join40(planV2Dir, "plan.json"),
|
|
18489
18806
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
18490
18807
|
planHash: manifest.planHash,
|
|
18491
18808
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -18621,7 +18938,7 @@ function parsePlanV2Manifest(value) {
|
|
|
18621
18938
|
return parsed;
|
|
18622
18939
|
}
|
|
18623
18940
|
function readPlanV2Manifest(planV2Dir) {
|
|
18624
|
-
const manifestPath =
|
|
18941
|
+
const manifestPath = join40(planV2Dir, "plan.json");
|
|
18625
18942
|
if (!existsSync26(manifestPath)) {
|
|
18626
18943
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18627
18944
|
}
|
|
@@ -18644,7 +18961,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18644
18961
|
if (!existsSync26(sourcePath)) {
|
|
18645
18962
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18646
18963
|
}
|
|
18647
|
-
const stats =
|
|
18964
|
+
const stats = statSync11(sourcePath);
|
|
18648
18965
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
18649
18966
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
18650
18967
|
}
|
|
@@ -18667,16 +18984,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18667
18984
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
18668
18985
|
}));
|
|
18669
18986
|
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18670
|
-
const tempDir = mkdtempSync7(
|
|
18987
|
+
const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
|
|
18671
18988
|
try {
|
|
18672
18989
|
for (const { artifact, sourcePath } of verified) {
|
|
18673
|
-
const destinationPath =
|
|
18990
|
+
const destinationPath = join40(tempDir, ...artifact.path.split("/"));
|
|
18674
18991
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18675
18992
|
copyFileSync7(sourcePath, destinationPath);
|
|
18676
18993
|
}
|
|
18677
18994
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
18678
18995
|
writeFileSync11(
|
|
18679
|
-
|
|
18996
|
+
join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
18680
18997
|
canonicalJsonStringify({
|
|
18681
18998
|
manifest,
|
|
18682
18999
|
target,
|
|
@@ -18686,7 +19003,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18686
19003
|
);
|
|
18687
19004
|
renameWithRetry8(tempDir, destinationDir);
|
|
18688
19005
|
} catch (error) {
|
|
18689
|
-
|
|
19006
|
+
rmSync16(tempDir, { recursive: true, force: true });
|
|
18690
19007
|
throw error;
|
|
18691
19008
|
}
|
|
18692
19009
|
return {
|
|
@@ -18699,7 +19016,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18699
19016
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
18700
19017
|
// before the container change carries the legacy name, and materializing it
|
|
18701
19018
|
// under the new one would point at a file that was never written.
|
|
18702
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19019
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
|
|
18703
19020
|
};
|
|
18704
19021
|
}
|
|
18705
19022
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -18718,7 +19035,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
18718
19035
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
18719
19036
|
}
|
|
18720
19037
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
18721
|
-
const markerPath =
|
|
19038
|
+
const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
18722
19039
|
if (!existsSync26(markerPath)) return null;
|
|
18723
19040
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
18724
19041
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -18736,8 +19053,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18736
19053
|
assertRemoteSources(remoteSources, required);
|
|
18737
19054
|
for (const artifact of required) {
|
|
18738
19055
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
18739
|
-
const path =
|
|
18740
|
-
if (!existsSync26(path) ||
|
|
19056
|
+
const path = join40(planDir, ...artifact.path.split("/"));
|
|
19057
|
+
if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
|
|
18741
19058
|
throw new PlanV2IntegrityError(
|
|
18742
19059
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
18743
19060
|
);
|
|
@@ -18750,9 +19067,9 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18750
19067
|
}
|
|
18751
19068
|
|
|
18752
19069
|
// src/services/distributed/planV2Execution.ts
|
|
18753
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19070
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
|
|
18754
19071
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
18755
|
-
import { join as
|
|
19072
|
+
import { join as join43 } from "node:path";
|
|
18756
19073
|
|
|
18757
19074
|
// src/services/distributed/assemble.ts
|
|
18758
19075
|
import {
|
|
@@ -18762,11 +19079,11 @@ import {
|
|
|
18762
19079
|
mkdtempSync as mkdtempSync8,
|
|
18763
19080
|
readFileSync as readFileSync14,
|
|
18764
19081
|
readdirSync as readdirSync10,
|
|
18765
|
-
rmSync as
|
|
18766
|
-
statSync as
|
|
19082
|
+
rmSync as rmSync17,
|
|
19083
|
+
statSync as statSync12,
|
|
18767
19084
|
writeFileSync as writeFileSync12
|
|
18768
19085
|
} from "node:fs";
|
|
18769
|
-
import { dirname as dirname17, join as
|
|
19086
|
+
import { dirname as dirname17, join as join41 } from "node:path";
|
|
18770
19087
|
import {
|
|
18771
19088
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
18772
19089
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -18802,7 +19119,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18802
19119
|
outputPath,
|
|
18803
19120
|
durationMs: Date.now() - start,
|
|
18804
19121
|
framesEncoded: plan2.totalFrames,
|
|
18805
|
-
fileSize:
|
|
19122
|
+
fileSize: statSync12(outputPath).size
|
|
18806
19123
|
};
|
|
18807
19124
|
}
|
|
18808
19125
|
const format = plan2.dimensions.format;
|
|
@@ -18830,7 +19147,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18830
19147
|
};
|
|
18831
19148
|
try {
|
|
18832
19149
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
18833
|
-
const finalPath =
|
|
19150
|
+
const finalPath = join41(workDir, `output.${format}`);
|
|
18834
19151
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
18835
19152
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
18836
19153
|
renameWithRetry9(finalPath, outputPath);
|
|
@@ -18841,7 +19158,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18841
19158
|
});
|
|
18842
19159
|
} finally {
|
|
18843
19160
|
try {
|
|
18844
|
-
|
|
19161
|
+
rmSync17(workDir, { recursive: true, force: true });
|
|
18845
19162
|
} catch (err) {
|
|
18846
19163
|
log.warn("[assemble] failed to remove work dir", {
|
|
18847
19164
|
workDir,
|
|
@@ -18849,7 +19166,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18849
19166
|
});
|
|
18850
19167
|
}
|
|
18851
19168
|
}
|
|
18852
|
-
const fileSize = existsSync27(outputPath) ?
|
|
19169
|
+
const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
|
|
18853
19170
|
return {
|
|
18854
19171
|
outputPath,
|
|
18855
19172
|
durationMs: Date.now() - start,
|
|
@@ -18858,8 +19175,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18858
19175
|
};
|
|
18859
19176
|
}
|
|
18860
19177
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
18861
|
-
const planJsonPath =
|
|
18862
|
-
const chunksJsonPath =
|
|
19178
|
+
const planJsonPath = join41(planDir, "plan.json");
|
|
19179
|
+
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
18863
19180
|
if (!existsSync27(planJsonPath)) {
|
|
18864
19181
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
18865
19182
|
}
|
|
@@ -18907,7 +19224,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
18907
19224
|
return args;
|
|
18908
19225
|
}
|
|
18909
19226
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
18910
|
-
const cfrOutputPath =
|
|
19227
|
+
const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
|
|
18911
19228
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
18912
19229
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
18913
19230
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -18934,7 +19251,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
18934
19251
|
);
|
|
18935
19252
|
return;
|
|
18936
19253
|
}
|
|
18937
|
-
const paddedAudioPath =
|
|
19254
|
+
const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
|
|
18938
19255
|
const padTrimResult = await ctx.timed(
|
|
18939
19256
|
"audioMs",
|
|
18940
19257
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -18974,7 +19291,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
18974
19291
|
}
|
|
18975
19292
|
}
|
|
18976
19293
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
18977
|
-
const listPath =
|
|
19294
|
+
const listPath = join41(workDir, "concat-list.txt");
|
|
18978
19295
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
18979
19296
|
writeFileSync12(listPath, `${body}
|
|
18980
19297
|
`, "utf-8");
|
|
@@ -19012,11 +19329,11 @@ function stitchesSeparateAudio(planDir) {
|
|
|
19012
19329
|
return lockedAudio(planDir).separateAudio === true;
|
|
19013
19330
|
}
|
|
19014
19331
|
function planFormat(planDir) {
|
|
19015
|
-
const plan2 = JSON.parse(readFileSync14(
|
|
19332
|
+
const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
|
|
19016
19333
|
return plan2.dimensions.format;
|
|
19017
19334
|
}
|
|
19018
19335
|
function lockedAudio(planDir) {
|
|
19019
|
-
const encoderJsonPath =
|
|
19336
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19020
19337
|
if (!existsSync27(encoderJsonPath)) return {};
|
|
19021
19338
|
return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
|
|
19022
19339
|
}
|
|
@@ -19026,7 +19343,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
19026
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.`
|
|
19027
19344
|
);
|
|
19028
19345
|
}
|
|
19029
|
-
const encoderJsonPath =
|
|
19346
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19030
19347
|
if (!existsSync27(encoderJsonPath)) {
|
|
19031
19348
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
19032
19349
|
}
|
|
@@ -19039,11 +19356,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19039
19356
|
return encoderJson;
|
|
19040
19357
|
}
|
|
19041
19358
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19042
|
-
if (existsSync27(outputPath))
|
|
19359
|
+
if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
|
|
19043
19360
|
mkdirSync17(outputPath, { recursive: true });
|
|
19044
19361
|
let globalIdx = 0;
|
|
19045
19362
|
for (const chunkDir of chunkPaths) {
|
|
19046
|
-
if (!
|
|
19363
|
+
if (!statSync12(chunkDir).isDirectory()) {
|
|
19047
19364
|
throw new Error(
|
|
19048
19365
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
19049
19366
|
);
|
|
@@ -19053,8 +19370,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19053
19370
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
19054
19371
|
}
|
|
19055
19372
|
for (const frame of frames) {
|
|
19056
|
-
const dst =
|
|
19057
|
-
cpSync3(
|
|
19373
|
+
const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
19374
|
+
cpSync3(join41(chunkDir, frame), dst);
|
|
19058
19375
|
globalIdx += 1;
|
|
19059
19376
|
}
|
|
19060
19377
|
}
|
|
@@ -19064,13 +19381,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19064
19381
|
);
|
|
19065
19382
|
}
|
|
19066
19383
|
if (audioPath !== null && existsSync27(audioPath)) {
|
|
19067
|
-
const sidecar =
|
|
19384
|
+
const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
|
|
19068
19385
|
cpSync3(audioPath, sidecar);
|
|
19069
19386
|
}
|
|
19070
19387
|
let fileSize = 0;
|
|
19071
19388
|
for (const name of readdirSync10(outputPath)) {
|
|
19072
19389
|
try {
|
|
19073
|
-
fileSize +=
|
|
19390
|
+
fileSize += statSync12(join41(outputPath, name)).size;
|
|
19074
19391
|
} catch {
|
|
19075
19392
|
}
|
|
19076
19393
|
}
|
|
@@ -19084,9 +19401,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19084
19401
|
|
|
19085
19402
|
// src/services/distributed/renderChunk.ts
|
|
19086
19403
|
import { randomBytes } from "node:crypto";
|
|
19087
|
-
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";
|
|
19088
19405
|
import { cpus as cpus3 } from "node:os";
|
|
19089
|
-
import { join as
|
|
19406
|
+
import { join as join42 } from "node:path";
|
|
19090
19407
|
import {
|
|
19091
19408
|
assertSwiftShader,
|
|
19092
19409
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -19205,7 +19522,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
19205
19522
|
if (kind === "file") return sha256Hex(readFileSync15(outputPath));
|
|
19206
19523
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19207
19524
|
const lines = entries.map(
|
|
19208
|
-
(name) => `${name}\0${sha256Hex(readFileSync15(
|
|
19525
|
+
(name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
|
|
19209
19526
|
);
|
|
19210
19527
|
return sha256Hex(lines.join("\0"));
|
|
19211
19528
|
}
|
|
@@ -19222,9 +19539,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19222
19539
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19223
19540
|
const start = Date.now();
|
|
19224
19541
|
const log = defaultLogger;
|
|
19225
|
-
const planJsonPath =
|
|
19226
|
-
const encoderJsonPath =
|
|
19227
|
-
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");
|
|
19228
19545
|
if (!existsSync28(planJsonPath)) {
|
|
19229
19546
|
throw new RenderChunkValidationError(
|
|
19230
19547
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19250,7 +19567,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19250
19567
|
}
|
|
19251
19568
|
const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19252
19569
|
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
19253
|
-
const videosJsonPath =
|
|
19570
|
+
const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19254
19571
|
let planVideos = null;
|
|
19255
19572
|
if (existsSync28(videosJsonPath)) {
|
|
19256
19573
|
try {
|
|
@@ -19282,7 +19599,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19282
19599
|
}
|
|
19283
19600
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19284
19601
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19285
|
-
const compiledDir =
|
|
19602
|
+
const compiledDir = join42(planDir, "compiled");
|
|
19286
19603
|
if (!existsSync28(compiledDir)) {
|
|
19287
19604
|
throw new RenderChunkValidationError(
|
|
19288
19605
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19340,7 +19657,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19340
19657
|
};
|
|
19341
19658
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19342
19659
|
mkdirSync18(workDir, { recursive: true });
|
|
19343
|
-
const framesDir =
|
|
19660
|
+
const framesDir = join42(workDir, "captured-frames");
|
|
19344
19661
|
mkdirSync18(framesDir, { recursive: true });
|
|
19345
19662
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19346
19663
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
@@ -19365,7 +19682,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19365
19682
|
dimensions: plan2.dimensions,
|
|
19366
19683
|
encoder,
|
|
19367
19684
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19368
|
-
html: readFileSync15(
|
|
19685
|
+
html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
|
|
19369
19686
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19370
19687
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19371
19688
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19490,7 +19807,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19490
19807
|
if (isPngSequence) {
|
|
19491
19808
|
if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
|
|
19492
19809
|
} else {
|
|
19493
|
-
const outDir =
|
|
19810
|
+
const outDir = join42(outputChunkPath, "..");
|
|
19494
19811
|
if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
|
|
19495
19812
|
}
|
|
19496
19813
|
const encodeInput = {
|
|
@@ -19594,7 +19911,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19594
19911
|
return observedChunkCaptureMode(capturePerfs);
|
|
19595
19912
|
},
|
|
19596
19913
|
resetForScreenshotRetry: () => {
|
|
19597
|
-
|
|
19914
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19598
19915
|
mkdirSync18(framesDir, { recursive: true });
|
|
19599
19916
|
capturePerfs.length = 0;
|
|
19600
19917
|
captureAttempts.length = 0;
|
|
@@ -19637,8 +19954,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19637
19954
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
19638
19955
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
19639
19956
|
});
|
|
19640
|
-
|
|
19641
|
-
|
|
19957
|
+
rmSync18(videoOnlyPath, { force: true });
|
|
19958
|
+
rmSync18(audioScratchPath, { force: true });
|
|
19642
19959
|
}
|
|
19643
19960
|
encodeStageMs = Date.now() - encodeStarted;
|
|
19644
19961
|
} finally {
|
|
@@ -19686,8 +20003,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19686
20003
|
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
19687
20004
|
`, "utf-8");
|
|
19688
20005
|
try {
|
|
19689
|
-
|
|
19690
|
-
|
|
20006
|
+
rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20007
|
+
rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
19691
20008
|
} catch (err) {
|
|
19692
20009
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
19693
20010
|
workDir,
|
|
@@ -19716,18 +20033,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19716
20033
|
|
|
19717
20034
|
// src/services/distributed/planV2Execution.ts
|
|
19718
20035
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
19719
|
-
const workRoot = mkdtempSync9(
|
|
19720
|
-
const materializedPlanDir =
|
|
20036
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
20037
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19721
20038
|
try {
|
|
19722
20039
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
19723
20040
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
19724
20041
|
} finally {
|
|
19725
|
-
|
|
20042
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19726
20043
|
}
|
|
19727
20044
|
}
|
|
19728
20045
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
19729
|
-
const workRoot = mkdtempSync9(
|
|
19730
|
-
const materializedPlanDir =
|
|
20046
|
+
const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
20047
|
+
const materializedPlanDir = join43(workRoot, "plan");
|
|
19731
20048
|
try {
|
|
19732
20049
|
const materialized = materializePlanV2Target(
|
|
19733
20050
|
planV2Dir,
|
|
@@ -19742,7 +20059,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
19742
20059
|
options
|
|
19743
20060
|
);
|
|
19744
20061
|
} finally {
|
|
19745
|
-
|
|
20062
|
+
rmSync19(workRoot, { recursive: true, force: true });
|
|
19746
20063
|
}
|
|
19747
20064
|
}
|
|
19748
20065
|
|