@hyperframes/producer 0.9.16 → 0.9.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/distributed.js +366 -296
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -62
- package/dist/index.js +926 -549
- package/dist/public-server.js +677 -265
- package/dist/services/distributed/chunkFrames.d.ts +1 -2
- package/dist/services/render/deferredVideoFrames.d.ts +42 -0
- package/dist/services/render/observability.d.ts +2 -0
- package/dist/services/render/segmentVideoFrames.d.ts +41 -0
- package/dist/services/render/stages/captureSegmentedStage.d.ts +5 -2
- package/dist/services/render/stages/captureStreamingStage.d.ts +7 -0
- package/dist/services/render/videoFrameWindow.d.ts +12 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
rmSync as rmSync7,
|
|
15
15
|
writeFileSync as writeFileSync8
|
|
16
16
|
} from "node:fs";
|
|
17
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as
|
|
17
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as sep2 } from "node:path";
|
|
18
18
|
import {
|
|
19
19
|
fpsToNumber as fpsToNumber9,
|
|
20
20
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
closeCaptureSession as closeCaptureSession8,
|
|
98
98
|
createCaptureSession as createCaptureSession8,
|
|
99
99
|
initializeSession as initializeSession8,
|
|
100
|
-
createVideoFrameInjector,
|
|
100
|
+
createVideoFrameInjector as createVideoFrameInjector2,
|
|
101
101
|
getEncoderPreset as getEncoderPreset3,
|
|
102
102
|
distributeFrames as distributeFrames2,
|
|
103
103
|
distributeFramesInterleaved as distributeFramesInterleaved2,
|
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
referenceBackgroundResult,
|
|
129
129
|
createReferenceBackgroundRecorder
|
|
130
130
|
} from "@hyperframes/engine";
|
|
131
|
-
import { join as
|
|
131
|
+
import { join as join16, dirname as dirname10, resolve as resolve7 } from "path";
|
|
132
132
|
import { randomUUID } from "crypto";
|
|
133
133
|
|
|
134
134
|
// src/services/fileServer.ts
|
|
@@ -1318,6 +1318,7 @@ import {
|
|
|
1318
1318
|
getCapturePerfSummary as getCapturePerfSummary2,
|
|
1319
1319
|
initializeSession as initializeSession2,
|
|
1320
1320
|
prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
|
|
1321
|
+
resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
|
|
1321
1322
|
shouldDisableBrowserPoolForParallelWorker,
|
|
1322
1323
|
spawnStreamingEncoder as spawnStreamingEncoder2
|
|
1323
1324
|
} from "@hyperframes/engine";
|
|
@@ -1546,6 +1547,85 @@ async function measureCaptureCostFromSession(session, totalFrames, fps, log) {
|
|
|
1546
1547
|
};
|
|
1547
1548
|
}
|
|
1548
1549
|
|
|
1550
|
+
// src/services/render/deferredVideoFrames.ts
|
|
1551
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1552
|
+
import {
|
|
1553
|
+
createFrameLookupTable,
|
|
1554
|
+
extractVideoFramesRange,
|
|
1555
|
+
runVideoExtractionWithRetry
|
|
1556
|
+
} from "@hyperframes/engine";
|
|
1557
|
+
function contiguousRuns(sortedIndexes) {
|
|
1558
|
+
const runs2 = [];
|
|
1559
|
+
for (const index of sortedIndexes) {
|
|
1560
|
+
const last = runs2[runs2.length - 1];
|
|
1561
|
+
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
1562
|
+
else runs2.push({ firstFrame: index, frames: 1 });
|
|
1563
|
+
}
|
|
1564
|
+
return runs2;
|
|
1565
|
+
}
|
|
1566
|
+
function shownDeferredFrameIndexes(source, frames, fps) {
|
|
1567
|
+
const shown = /* @__PURE__ */ new Map();
|
|
1568
|
+
const deferred = source.extracted.filter((video) => video.deferredRange);
|
|
1569
|
+
if (deferred.length === 0) return shown;
|
|
1570
|
+
const table = createFrameLookupTable(
|
|
1571
|
+
source.videos,
|
|
1572
|
+
deferred.map((video) => ({
|
|
1573
|
+
...video,
|
|
1574
|
+
outputDir: "",
|
|
1575
|
+
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
1576
|
+
})),
|
|
1577
|
+
resolveRenderFpsConfig(fps).value
|
|
1578
|
+
);
|
|
1579
|
+
for (const frame of frames) {
|
|
1580
|
+
const globalTime = frame * fps.den / fps.num;
|
|
1581
|
+
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
1582
|
+
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
1583
|
+
indexes.add(payload.frameIndex);
|
|
1584
|
+
shown.set(videoId, indexes);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
return shown;
|
|
1588
|
+
}
|
|
1589
|
+
async function extractDeferredFrames(input) {
|
|
1590
|
+
const deferred = input.extracted.filter((video) => video.deferredRange);
|
|
1591
|
+
if (deferred.length === 0) return [];
|
|
1592
|
+
const shown = shownDeferredFrameIndexes(input, input.frames, input.fps);
|
|
1593
|
+
return Promise.all(
|
|
1594
|
+
deferred.map(async (video) => {
|
|
1595
|
+
const outputDir = join4(input.outputRoot, video.videoId);
|
|
1596
|
+
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
1597
|
+
if (indexes.length === 0) {
|
|
1598
|
+
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
1599
|
+
}
|
|
1600
|
+
const range = video.deferredRange;
|
|
1601
|
+
const extracted = await extractVideoFramesRange(
|
|
1602
|
+
input.sourceOf(video),
|
|
1603
|
+
video.videoId,
|
|
1604
|
+
range.startTime,
|
|
1605
|
+
range.durationSeconds,
|
|
1606
|
+
{
|
|
1607
|
+
fps: input.fps,
|
|
1608
|
+
outputDir: input.outputRoot,
|
|
1609
|
+
format: range.format,
|
|
1610
|
+
frameRanges: contiguousRuns(indexes),
|
|
1611
|
+
shownBox: input.shownBoxes?.get(video.videoId)
|
|
1612
|
+
},
|
|
1613
|
+
input.signal,
|
|
1614
|
+
input.cfg
|
|
1615
|
+
);
|
|
1616
|
+
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
1617
|
+
if (missing !== void 0) throw input.onMissingFrame(video, missing);
|
|
1618
|
+
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
1619
|
+
})
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// src/services/render/segmentVideoFrames.ts
|
|
1624
|
+
import {
|
|
1625
|
+
createFrameLookupTable as createFrameLookupTable2,
|
|
1626
|
+
createVideoFrameInjector
|
|
1627
|
+
} from "@hyperframes/engine";
|
|
1628
|
+
|
|
1549
1629
|
// src/services/render/segmentManifest.ts
|
|
1550
1630
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
1551
1631
|
|
|
@@ -1914,7 +1994,7 @@ import {
|
|
|
1914
1994
|
|
|
1915
1995
|
// src/services/render/stages/compileStage.ts
|
|
1916
1996
|
import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1917
|
-
import { join as
|
|
1997
|
+
import { join as join8 } from "node:path";
|
|
1918
1998
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1919
1999
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
1920
2000
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
@@ -1923,7 +2003,7 @@ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolu
|
|
|
1923
2003
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1924
2004
|
import { createHash as createHash4 } from "node:crypto";
|
|
1925
2005
|
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1926
|
-
import { join as
|
|
2006
|
+
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1927
2007
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1928
2008
|
import {
|
|
1929
2009
|
COMPOSITION_SOURCE_URL,
|
|
@@ -2170,7 +2250,7 @@ import {
|
|
|
2170
2250
|
// src/services/animatedGifPrep.ts
|
|
2171
2251
|
import { createHash as createHash2 } from "node:crypto";
|
|
2172
2252
|
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
2173
|
-
import { dirname as
|
|
2253
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
|
|
2174
2254
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
2175
2255
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
2176
2256
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -2326,11 +2406,11 @@ async function runAnimatedGifTranscode(request) {
|
|
|
2326
2406
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
2327
2407
|
}
|
|
2328
2408
|
async function ensurePreparedWebm(input) {
|
|
2329
|
-
if (!existsSync4(
|
|
2330
|
-
mkdirSync2(
|
|
2409
|
+
if (!existsSync4(dirname4(input.cachePath))) {
|
|
2410
|
+
mkdirSync2(dirname4(input.cachePath), { recursive: true });
|
|
2331
2411
|
}
|
|
2332
|
-
if (!existsSync4(
|
|
2333
|
-
mkdirSync2(
|
|
2412
|
+
if (!existsSync4(dirname4(input.outputPath))) {
|
|
2413
|
+
mkdirSync2(dirname4(input.outputPath), { recursive: true });
|
|
2334
2414
|
}
|
|
2335
2415
|
if (!isUsableFile(input.cachePath)) {
|
|
2336
2416
|
const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -2413,7 +2493,7 @@ function replaceImageWithVideo(input) {
|
|
|
2413
2493
|
return video;
|
|
2414
2494
|
}
|
|
2415
2495
|
async function prepareAnimatedGifInputs(html, options) {
|
|
2416
|
-
const outputDir = options.outputDir ??
|
|
2496
|
+
const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
2417
2497
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
2418
2498
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
2419
2499
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -2440,8 +2520,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2440
2520
|
);
|
|
2441
2521
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
2442
2522
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
2443
|
-
const cachePath =
|
|
2444
|
-
const outputPath =
|
|
2523
|
+
const cachePath = join5(cacheDir, filename);
|
|
2524
|
+
const outputPath = join5(outputDir, filename);
|
|
2445
2525
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2446
2526
|
await ensurePreparedWebm({
|
|
2447
2527
|
sourcePath,
|
|
@@ -2486,7 +2566,7 @@ import {
|
|
|
2486
2566
|
statSync as statSync3,
|
|
2487
2567
|
writeFileSync as writeFileSync2
|
|
2488
2568
|
} from "node:fs";
|
|
2489
|
-
import { dirname as
|
|
2569
|
+
import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
|
|
2490
2570
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
2491
2571
|
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
2492
2572
|
|
|
@@ -2732,8 +2812,8 @@ function isUsableFile2(path) {
|
|
|
2732
2812
|
}
|
|
2733
2813
|
}
|
|
2734
2814
|
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
2735
|
-
mkdirSync3(
|
|
2736
|
-
mkdirSync3(
|
|
2815
|
+
mkdirSync3(dirname5(cachePath), { recursive: true });
|
|
2816
|
+
mkdirSync3(dirname5(outputPath), { recursive: true });
|
|
2737
2817
|
if (!isUsableFile2(cachePath)) {
|
|
2738
2818
|
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
2739
2819
|
const inlinePath = `${tmpPath}.heic`;
|
|
@@ -2760,7 +2840,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
2760
2840
|
}
|
|
2761
2841
|
}
|
|
2762
2842
|
async function prepareHeifImageInputs(html, options) {
|
|
2763
|
-
const outputDir = options.outputDir ??
|
|
2843
|
+
const outputDir = options.outputDir ?? join6(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
2764
2844
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
2765
2845
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
2766
2846
|
const preparedAssets = /* @__PURE__ */ new Map();
|
|
@@ -2781,7 +2861,7 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
2781
2861
|
await Promise.all(
|
|
2782
2862
|
[...unique].map(
|
|
2783
2863
|
([filename, source]) => withMediaProbeSlot(
|
|
2784
|
-
() => ensurePreparedJpeg(source,
|
|
2864
|
+
() => ensurePreparedJpeg(source, join6(cacheDir, filename), join6(outputDir, filename), options)
|
|
2785
2865
|
).catch((error) => {
|
|
2786
2866
|
if (!options.onFailure) throw error;
|
|
2787
2867
|
failed.add(filename);
|
|
@@ -2793,8 +2873,8 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
2793
2873
|
if (failed.has(filename)) continue;
|
|
2794
2874
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2795
2875
|
img.setAttribute("src", outputSrc);
|
|
2796
|
-
preparedAssets.set(outputSrc,
|
|
2797
|
-
preparedImages.push({ sourceSrc: src, outputSrc, outputPath:
|
|
2876
|
+
preparedAssets.set(outputSrc, join6(outputDir, filename));
|
|
2877
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join6(outputDir, filename) });
|
|
2798
2878
|
}
|
|
2799
2879
|
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
2800
2880
|
}
|
|
@@ -3118,7 +3198,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
3118
3198
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
|
|
3119
3199
|
let filePath = src;
|
|
3120
3200
|
if (isHttpUrl(src)) {
|
|
3121
|
-
const remoteDir =
|
|
3201
|
+
const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
3122
3202
|
if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3123
3203
|
try {
|
|
3124
3204
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
@@ -3129,7 +3209,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3129
3209
|
}
|
|
3130
3210
|
} else {
|
|
3131
3211
|
filePath = decodedUrlPath(src);
|
|
3132
|
-
if (!filePath.startsWith("/")) filePath =
|
|
3212
|
+
if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
|
|
3133
3213
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
3134
3214
|
}
|
|
3135
3215
|
if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
|
|
@@ -3290,7 +3370,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3290
3370
|
workItems.map(async (item) => {
|
|
3291
3371
|
const { html: compiledSub } = await compileHtmlFile(
|
|
3292
3372
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
3293
|
-
|
|
3373
|
+
dirname6(item.filePath),
|
|
3294
3374
|
downloadDir,
|
|
3295
3375
|
void 0,
|
|
3296
3376
|
urlByAbsentLocalPath,
|
|
@@ -3767,7 +3847,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
3767
3847
|
return downloadAndRewriteUrls(
|
|
3768
3848
|
urlSet,
|
|
3769
3849
|
html,
|
|
3770
|
-
|
|
3850
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3771
3851
|
"Remote media download failed",
|
|
3772
3852
|
"Localized remote media source(s)"
|
|
3773
3853
|
);
|
|
@@ -3782,7 +3862,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
3782
3862
|
return downloadAndRewriteUrls(
|
|
3783
3863
|
urlSet,
|
|
3784
3864
|
html,
|
|
3785
|
-
|
|
3865
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3786
3866
|
"Remote image download failed",
|
|
3787
3867
|
"Localized remote image source(s)"
|
|
3788
3868
|
);
|
|
@@ -3798,7 +3878,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
3798
3878
|
return downloadAndRewriteUrls(
|
|
3799
3879
|
urlSet,
|
|
3800
3880
|
html,
|
|
3801
|
-
|
|
3881
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3802
3882
|
"Remote background-image download failed",
|
|
3803
3883
|
"Localized remote background-image(s)",
|
|
3804
3884
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -3908,7 +3988,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
3908
3988
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3909
3989
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3910
3990
|
const path = decodedUrlPath(url);
|
|
3911
|
-
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(
|
|
3991
|
+
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
|
|
3912
3992
|
return isPathInside2(file, projectDir) && existsSync6(file);
|
|
3913
3993
|
});
|
|
3914
3994
|
if (family && shipsFile) families.push(family);
|
|
@@ -3950,7 +4030,7 @@ function localizeFontFaceUrls(html, downloadDir) {
|
|
|
3950
4030
|
return downloadAndRewriteUrls(
|
|
3951
4031
|
collectFontFaceUrls(html),
|
|
3952
4032
|
html,
|
|
3953
|
-
|
|
4033
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3954
4034
|
"Remote font download failed",
|
|
3955
4035
|
"Localized remote font face(s)",
|
|
3956
4036
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -4624,10 +4704,10 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
4624
4704
|
}
|
|
4625
4705
|
function resolveEntryHtmlPath(input) {
|
|
4626
4706
|
const { projectDir, workDir, entryFile, log } = input;
|
|
4627
|
-
const htmlPath =
|
|
4707
|
+
const htmlPath = join8(projectDir, entryFile);
|
|
4628
4708
|
const rawEntry = readTextFile2(htmlPath);
|
|
4629
4709
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4630
|
-
const projectIndexPath =
|
|
4710
|
+
const projectIndexPath = join8(projectDir, "index.html");
|
|
4631
4711
|
if (!existsSync7(projectIndexPath)) {
|
|
4632
4712
|
throw new Error(
|
|
4633
4713
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -4643,7 +4723,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
4643
4723
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
4644
4724
|
);
|
|
4645
4725
|
}
|
|
4646
|
-
const wrapperPath =
|
|
4726
|
+
const wrapperPath = join8(workDir, "standalone-entry.html");
|
|
4647
4727
|
writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
|
|
4648
4728
|
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
4649
4729
|
return wrapperPath;
|
|
@@ -4664,7 +4744,7 @@ async function runCompileStage(input) {
|
|
|
4664
4744
|
} = input;
|
|
4665
4745
|
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
4666
4746
|
const compileStart = Date.now();
|
|
4667
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
4747
|
+
const compiled = await compileForRender(projectDir, htmlPath, join8(workDir, "downloads"), {
|
|
4668
4748
|
log,
|
|
4669
4749
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
4670
4750
|
allowSystemFontCapture,
|
|
@@ -4672,7 +4752,7 @@ async function runCompileStage(input) {
|
|
|
4672
4752
|
variables: input.variables,
|
|
4673
4753
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
4674
4754
|
pageMotionBlur: input.pageMotionBlur,
|
|
4675
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
4755
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join8(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
4676
4756
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
4677
4757
|
});
|
|
4678
4758
|
assertNotAborted();
|
|
@@ -4729,7 +4809,7 @@ async function runCompileStage(input) {
|
|
|
4729
4809
|
};
|
|
4730
4810
|
await preflightCompositionAssetMediaTypes({
|
|
4731
4811
|
projectDir,
|
|
4732
|
-
compiledDir:
|
|
4812
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4733
4813
|
composition,
|
|
4734
4814
|
signal: abortSignal
|
|
4735
4815
|
});
|
|
@@ -4775,7 +4855,7 @@ async function runCompileStage(input) {
|
|
|
4775
4855
|
|
|
4776
4856
|
// src/services/render/stages/probeStage.ts
|
|
4777
4857
|
import { createHash as createHash5 } from "node:crypto";
|
|
4778
|
-
import { basename as basename4, join as
|
|
4858
|
+
import { basename as basename4, join as join9 } from "node:path";
|
|
4779
4859
|
import { parseHTML as parseHTML6 } from "linkedom";
|
|
4780
4860
|
import {
|
|
4781
4861
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -4984,7 +5064,7 @@ async function runProbeStage(input) {
|
|
|
4984
5064
|
});
|
|
4985
5065
|
fileServer = await createFileServer({
|
|
4986
5066
|
projectDir,
|
|
4987
|
-
compiledDir:
|
|
5067
|
+
compiledDir: join9(workDir, "compiled"),
|
|
4988
5068
|
port: 0,
|
|
4989
5069
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
4990
5070
|
fps: job.config.fps
|
|
@@ -5014,7 +5094,7 @@ async function runProbeStage(input) {
|
|
|
5014
5094
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
5015
5095
|
probeSession = await createCaptureSession4(
|
|
5016
5096
|
fileServer.url,
|
|
5017
|
-
|
|
5097
|
+
join9(workDir, "probe"),
|
|
5018
5098
|
captureOpts,
|
|
5019
5099
|
null,
|
|
5020
5100
|
probeCfg
|
|
@@ -5099,7 +5179,7 @@ async function runProbeStage(input) {
|
|
|
5099
5179
|
});
|
|
5100
5180
|
probeSession = await createCaptureSession4(
|
|
5101
5181
|
fileServer.url,
|
|
5102
|
-
|
|
5182
|
+
join9(workDir, "probe-screenshot"),
|
|
5103
5183
|
captureOpts,
|
|
5104
5184
|
null,
|
|
5105
5185
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -5137,7 +5217,7 @@ async function runProbeStage(input) {
|
|
|
5137
5217
|
compiled,
|
|
5138
5218
|
resolutions,
|
|
5139
5219
|
projectDir,
|
|
5140
|
-
|
|
5220
|
+
join9(workDir, "downloads")
|
|
5141
5221
|
);
|
|
5142
5222
|
assertNotAborted();
|
|
5143
5223
|
composition.videos = compiled.videos;
|
|
@@ -5328,7 +5408,7 @@ async function runProbeStage(input) {
|
|
|
5328
5408
|
}
|
|
5329
5409
|
await preflightCompositionAssetMediaTypes({
|
|
5330
5410
|
projectDir,
|
|
5331
|
-
compiledDir:
|
|
5411
|
+
compiledDir: join9(workDir, "compiled"),
|
|
5332
5412
|
composition,
|
|
5333
5413
|
signal: abortSignal
|
|
5334
5414
|
});
|
|
@@ -5495,10 +5575,10 @@ function validateDistributedDuration(input) {
|
|
|
5495
5575
|
|
|
5496
5576
|
// src/services/render/stages/extractVideosStage.ts
|
|
5497
5577
|
import { existsSync as existsSync8 } from "node:fs";
|
|
5498
|
-
import { join as
|
|
5578
|
+
import { join as join10 } from "node:path";
|
|
5499
5579
|
import {
|
|
5500
5580
|
classifyVideoExtractionError,
|
|
5501
|
-
createFrameLookupTable,
|
|
5581
|
+
createFrameLookupTable as createFrameLookupTable3,
|
|
5502
5582
|
detectTransfer,
|
|
5503
5583
|
extractAllVideoFrames,
|
|
5504
5584
|
extractMediaMetadata as extractMediaMetadata3,
|
|
@@ -5510,7 +5590,7 @@ import {
|
|
|
5510
5590
|
resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
|
|
5511
5591
|
isHttpUrl as isHttpUrl2,
|
|
5512
5592
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
5513
|
-
runVideoExtractionWithRetry,
|
|
5593
|
+
runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
|
|
5514
5594
|
safeVideoExtractionSourceIdentity,
|
|
5515
5595
|
videoAudioId as videoAudioId2
|
|
5516
5596
|
} from "@hyperframes/engine";
|
|
@@ -5771,7 +5851,7 @@ async function runExtractVideosStage(input) {
|
|
|
5771
5851
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5772
5852
|
if (!existsSync8(videoPath)) return null;
|
|
5773
5853
|
try {
|
|
5774
|
-
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await
|
|
5854
|
+
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
5775
5855
|
signal: abortSignal,
|
|
5776
5856
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5777
5857
|
onRetry: () => {
|
|
@@ -5841,14 +5921,14 @@ async function runExtractVideosStage(input) {
|
|
|
5841
5921
|
// because short boundary counts can differ by one frame.
|
|
5842
5922
|
{
|
|
5843
5923
|
fps: job.config.fps,
|
|
5844
|
-
outputDir:
|
|
5924
|
+
outputDir: join10(compiledDir, "__hyperframes_video_frames"),
|
|
5845
5925
|
format: job.config.videoFrameFormat ?? "auto",
|
|
5846
5926
|
toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
|
|
5847
5927
|
timelineEnd: composition.duration,
|
|
5848
5928
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5849
5929
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
5850
5930
|
deferRangeExtraction,
|
|
5851
|
-
contentKeyedDirs: [
|
|
5931
|
+
contentKeyedDirs: [join10(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
5852
5932
|
urlByAbsentLocalPath,
|
|
5853
5933
|
onProgress: ({ planned = 0, done = 0 }) => {
|
|
5854
5934
|
framesPlanned += planned;
|
|
@@ -5868,7 +5948,7 @@ async function runExtractVideosStage(input) {
|
|
|
5868
5948
|
materializeSymlinks
|
|
5869
5949
|
});
|
|
5870
5950
|
if (extractionResult.extracted.length > 0) {
|
|
5871
|
-
frameLookup =
|
|
5951
|
+
frameLookup = createFrameLookupTable3(
|
|
5872
5952
|
composition.videos,
|
|
5873
5953
|
extractionResult.extracted,
|
|
5874
5954
|
resolveRenderFpsConfig(job.config.fps).value
|
|
@@ -5956,7 +6036,7 @@ import {
|
|
|
5956
6036
|
|
|
5957
6037
|
// src/services/render/stages/audioStage.ts
|
|
5958
6038
|
import { existsSync as existsSync10 } from "node:fs";
|
|
5959
|
-
import { join as
|
|
6039
|
+
import { join as join13 } from "node:path";
|
|
5960
6040
|
import {
|
|
5961
6041
|
MIXED_AUDIO_FILENAME,
|
|
5962
6042
|
audioFailureDiagnostics,
|
|
@@ -5967,7 +6047,7 @@ import {
|
|
|
5967
6047
|
|
|
5968
6048
|
// src/services/render/audioDeliveryLimit.ts
|
|
5969
6049
|
import { existsSync as existsSync9 } from "node:fs";
|
|
5970
|
-
import { dirname as
|
|
6050
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
5971
6051
|
import {
|
|
5972
6052
|
applyAudioFxChain,
|
|
5973
6053
|
describeFfmpegFailure as describeFfmpegFailure3,
|
|
@@ -5978,7 +6058,7 @@ import {
|
|
|
5978
6058
|
// src/services/render/audioPadTrim.ts
|
|
5979
6059
|
import { spawn } from "node:child_process";
|
|
5980
6060
|
import { mkdtempSync } from "node:fs";
|
|
5981
|
-
import { dirname as
|
|
6061
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5982
6062
|
import {
|
|
5983
6063
|
DEFAULT_CONFIG,
|
|
5984
6064
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -6257,7 +6337,7 @@ async function constrainAacTruePeak(input) {
|
|
|
6257
6337
|
}
|
|
6258
6338
|
async function cutSource(input, scratchDir, attenuationDb) {
|
|
6259
6339
|
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
6260
|
-
const cutPath =
|
|
6340
|
+
const cutPath = join11(scratchDir, "source.wav");
|
|
6261
6341
|
try {
|
|
6262
6342
|
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
6263
6343
|
if (!result.success) throw new Error(result.error);
|
|
@@ -6271,8 +6351,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
|
|
|
6271
6351
|
async function enforceAacTruePeak(input) {
|
|
6272
6352
|
let scratchDir;
|
|
6273
6353
|
try {
|
|
6274
|
-
scratchDir = mkdtempSync(
|
|
6275
|
-
const correctedPath =
|
|
6354
|
+
scratchDir = mkdtempSync(join11(dirname7(input.audioPath), ".true-peak-"));
|
|
6355
|
+
const correctedPath = join11(scratchDir, "audio.m4a");
|
|
6276
6356
|
const measurements = [];
|
|
6277
6357
|
let attenuationDb = 0;
|
|
6278
6358
|
let sourcePeakDbfs;
|
|
@@ -6463,11 +6543,13 @@ async function runFfprobeJson(args, signal) {
|
|
|
6463
6543
|
}
|
|
6464
6544
|
|
|
6465
6545
|
// src/services/render/audioDeliveryLimit.ts
|
|
6466
|
-
var
|
|
6467
|
-
var
|
|
6546
|
+
var LIMIT_PASSES = 3;
|
|
6547
|
+
var LOWEST_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
|
|
6548
|
+
var PASS_HEADROOM_DB = 0.05;
|
|
6549
|
+
var limitChain = (ceiling) => ({
|
|
6468
6550
|
version: 1,
|
|
6469
|
-
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling
|
|
6470
|
-
};
|
|
6551
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
|
|
6552
|
+
});
|
|
6471
6553
|
async function limitDeliveryPeaks(input, deps = {
|
|
6472
6554
|
measure: measureTruePeakDbfs,
|
|
6473
6555
|
applyChain: applyAudioFxChain,
|
|
@@ -6479,15 +6561,39 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6479
6561
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
6480
6562
|
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
6481
6563
|
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
6482
|
-
const
|
|
6564
|
+
const alternating = ["a", "b"].map(
|
|
6565
|
+
(k) => join12(dirname8(sourcePath), `audio-source-limited-${k}.wav`)
|
|
6566
|
+
);
|
|
6567
|
+
let encodedFrom;
|
|
6568
|
+
let ceiling = AAC_DELIVERY_TRUE_PEAK_DBFS;
|
|
6569
|
+
for (let pass = 0; pass < LIMIT_PASSES; pass++) {
|
|
6570
|
+
const limitedPath = alternating[pass % 2];
|
|
6571
|
+
const result = await limitPass(input, deps, ceiling, limitedPath);
|
|
6572
|
+
if ("encodeFailure" in result) return result.encodeFailure;
|
|
6573
|
+
if ("limitFailed" in result) break;
|
|
6574
|
+
encodedFrom = limitedPath;
|
|
6575
|
+
if (result.peak <= AAC_DELIVERY_TRUE_PEAK_DBFS || ceiling <= LOWEST_CEILING_DBTP) break;
|
|
6576
|
+
ceiling = Math.max(
|
|
6577
|
+
ceiling - (result.peak - AAC_DELIVERY_TRUE_PEAK_DBFS) - PASS_HEADROOM_DB,
|
|
6578
|
+
LOWEST_CEILING_DBTP
|
|
6579
|
+
);
|
|
6580
|
+
}
|
|
6581
|
+
if (encodedFrom) deps.rename(encodedFrom, sourcePath, { copyFallback: true });
|
|
6582
|
+
return { success: true };
|
|
6583
|
+
}
|
|
6584
|
+
async function limitPass(input, deps, ceiling, limitedPath) {
|
|
6585
|
+
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6483
6586
|
try {
|
|
6484
|
-
await deps.applyChain(sourcePath,
|
|
6587
|
+
await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
|
|
6588
|
+
trackId: "delivery",
|
|
6589
|
+
signal
|
|
6590
|
+
});
|
|
6485
6591
|
} catch (error) {
|
|
6486
6592
|
if (signal?.aborted) throw error;
|
|
6487
6593
|
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
6488
6594
|
error: error instanceof Error ? error.message : String(error)
|
|
6489
6595
|
});
|
|
6490
|
-
return {
|
|
6596
|
+
return { limitFailed: true };
|
|
6491
6597
|
}
|
|
6492
6598
|
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
6493
6599
|
signal,
|
|
@@ -6495,13 +6601,14 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6495
6601
|
});
|
|
6496
6602
|
if (!encoded.success) {
|
|
6497
6603
|
return {
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6604
|
+
encodeFailure: {
|
|
6605
|
+
success: false,
|
|
6606
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
6607
|
+
failureReason: encoded.failureReason
|
|
6608
|
+
}
|
|
6501
6609
|
};
|
|
6502
6610
|
}
|
|
6503
|
-
deps.
|
|
6504
|
-
return { success: true };
|
|
6611
|
+
return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
|
|
6505
6612
|
}
|
|
6506
6613
|
|
|
6507
6614
|
// src/services/render/stages/audioStage.ts
|
|
@@ -6527,7 +6634,7 @@ function runAudioStage(input) {
|
|
|
6527
6634
|
}
|
|
6528
6635
|
async function runAudioStageScoped(input) {
|
|
6529
6636
|
const stageStart = Date.now();
|
|
6530
|
-
const pcmSourcePath =
|
|
6637
|
+
const pcmSourcePath = join13(input.workDir, "audio-source.wav");
|
|
6531
6638
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6532
6639
|
let deliverySourcePath;
|
|
6533
6640
|
try {
|
|
@@ -6592,7 +6699,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6592
6699
|
assertNotAborted,
|
|
6593
6700
|
log
|
|
6594
6701
|
} = input;
|
|
6595
|
-
const audioOutputPath =
|
|
6702
|
+
const audioOutputPath = join13(workDir, MIXED_AUDIO_FILENAME);
|
|
6596
6703
|
let hasAudio = false;
|
|
6597
6704
|
let audioError;
|
|
6598
6705
|
let audioFailures;
|
|
@@ -6603,7 +6710,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6603
6710
|
audioResult = await processCompositionAudio(
|
|
6604
6711
|
audios,
|
|
6605
6712
|
projectDir,
|
|
6606
|
-
|
|
6713
|
+
join13(workDir, "audio-work"),
|
|
6607
6714
|
audioOutputPath,
|
|
6608
6715
|
duration,
|
|
6609
6716
|
abortSignal,
|
|
@@ -6672,7 +6779,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6672
6779
|
|
|
6673
6780
|
// src/services/render/stages/captureStage.ts
|
|
6674
6781
|
import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
|
|
6675
|
-
import { join as
|
|
6782
|
+
import { join as join14 } from "node:path";
|
|
6676
6783
|
import {
|
|
6677
6784
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
6678
6785
|
frameFileExtension,
|
|
@@ -6726,13 +6833,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
6726
6833
|
if (!existsSync11(workDir)) return total;
|
|
6727
6834
|
for (const attempt of readdirSync(workDir)) {
|
|
6728
6835
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6729
|
-
total += sampleDirectoryBytes(
|
|
6836
|
+
total += sampleDirectoryBytes(join14(workDir, attempt));
|
|
6730
6837
|
continue;
|
|
6731
6838
|
}
|
|
6732
6839
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
6733
|
-
const attemptDir =
|
|
6840
|
+
const attemptDir = join14(workDir, attempt);
|
|
6734
6841
|
for (const name of readdirSync(attemptDir)) {
|
|
6735
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
6842
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join14(attemptDir, name));
|
|
6736
6843
|
}
|
|
6737
6844
|
}
|
|
6738
6845
|
return total;
|
|
@@ -7069,7 +7176,7 @@ import {
|
|
|
7069
7176
|
writeFileSync as writeFileSync4
|
|
7070
7177
|
} from "node:fs";
|
|
7071
7178
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
7072
|
-
import { dirname as
|
|
7179
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7073
7180
|
import {
|
|
7074
7181
|
encodeFramesChunkedConcat,
|
|
7075
7182
|
encodeFramesFromDir,
|
|
@@ -7280,7 +7387,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
7280
7387
|
await new Promise((resolve10) => wake = resolve10);
|
|
7281
7388
|
}
|
|
7282
7389
|
if (signal.aborted) return;
|
|
7283
|
-
const mergedPath =
|
|
7390
|
+
const mergedPath = join15(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
7284
7391
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
7285
7392
|
landed.delete(next);
|
|
7286
7393
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -7363,11 +7470,11 @@ async function runEncodeStage(input) {
|
|
|
7363
7470
|
);
|
|
7364
7471
|
}
|
|
7365
7472
|
captured.forEach((name, i) => {
|
|
7366
|
-
const dst =
|
|
7367
|
-
copyFileSync4(
|
|
7473
|
+
const dst = join15(outputPath, formatExportFrameName(i, "png"));
|
|
7474
|
+
copyFileSync4(join15(framesDir, name), dst);
|
|
7368
7475
|
});
|
|
7369
7476
|
if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
|
|
7370
|
-
copyFileSync4(audioOutputPath,
|
|
7477
|
+
copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7371
7478
|
log.info(
|
|
7372
7479
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
7373
7480
|
);
|
|
@@ -7387,7 +7494,7 @@ async function runEncodeStage(input) {
|
|
|
7387
7494
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
7388
7495
|
fps: job.config.fps,
|
|
7389
7496
|
loop,
|
|
7390
|
-
palettePath:
|
|
7497
|
+
palettePath: join15(dirname9(videoOnlyPath), "gif-palette.png"),
|
|
7391
7498
|
preserveAlpha: needsAlpha,
|
|
7392
7499
|
signal: abortSignal,
|
|
7393
7500
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -7446,7 +7553,7 @@ function sampleDirectoryBytes(dir) {
|
|
|
7446
7553
|
continue;
|
|
7447
7554
|
}
|
|
7448
7555
|
for (const name of entries) {
|
|
7449
|
-
const full =
|
|
7556
|
+
const full = join16(current, name);
|
|
7450
7557
|
try {
|
|
7451
7558
|
const st = statSync5(full);
|
|
7452
7559
|
if (st.isDirectory()) {
|
|
@@ -7528,7 +7635,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7528
7635
|
const ranges = [];
|
|
7529
7636
|
let rangeStart = null;
|
|
7530
7637
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7531
|
-
const framePath =
|
|
7638
|
+
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7532
7639
|
const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
|
|
7533
7640
|
if (missing && rangeStart === null) {
|
|
7534
7641
|
rangeStart = frameIndex;
|
|
@@ -7545,7 +7652,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7545
7652
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
7546
7653
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
7547
7654
|
const batches = [];
|
|
7548
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
7655
|
+
const outputDirFor = (batchIndex, workerId) => join16(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
7549
7656
|
if (stride > 1) {
|
|
7550
7657
|
const spans = /* @__PURE__ */ new Map();
|
|
7551
7658
|
for (const range of ranges) {
|
|
@@ -7618,7 +7725,7 @@ function isRecoverableParallelCaptureError(error) {
|
|
|
7618
7725
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
7619
7726
|
let captured = 0;
|
|
7620
7727
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7621
|
-
const framePath =
|
|
7728
|
+
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7622
7729
|
if (existsSync13(framePath)) captured++;
|
|
7623
7730
|
}
|
|
7624
7731
|
return captured;
|
|
@@ -7644,7 +7751,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
7644
7751
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
7645
7752
|
});
|
|
7646
7753
|
pendingTransientRetry = false;
|
|
7647
|
-
const attemptWorkDir =
|
|
7754
|
+
const attemptWorkDir = join16(options.workDir, `capture-attempt-${attempt}`);
|
|
7648
7755
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
7649
7756
|
missingRanges,
|
|
7650
7757
|
currentWorkers,
|
|
@@ -7837,27 +7944,25 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
|
|
|
7837
7944
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
7838
7945
|
|
|
7839
7946
|
// src/services/distributed/planFrameCost.ts
|
|
7840
|
-
import { join as
|
|
7947
|
+
import { join as join19 } from "node:path";
|
|
7841
7948
|
import {
|
|
7842
7949
|
closeCaptureSession as closeCaptureSession9,
|
|
7843
7950
|
createCaptureSession as createCaptureSession9,
|
|
7844
|
-
createFrameLookupTable as
|
|
7951
|
+
createFrameLookupTable as createFrameLookupTable4,
|
|
7845
7952
|
initializeSession as initializeSession9
|
|
7846
7953
|
} from "@hyperframes/engine";
|
|
7847
7954
|
|
|
7848
7955
|
// src/services/distributed/chunkFrames.ts
|
|
7849
7956
|
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
|
|
7850
|
-
import { extname as extname2, join as
|
|
7957
|
+
import { extname as extname2, join as join18 } from "node:path";
|
|
7851
7958
|
import {
|
|
7852
7959
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
7853
|
-
|
|
7854
|
-
createVideoFrameInjector as createVideoFrameInjector2,
|
|
7855
|
-
extractVideoFramesRange
|
|
7960
|
+
createVideoFrameInjector as createVideoFrameInjector3
|
|
7856
7961
|
} from "@hyperframes/engine";
|
|
7857
7962
|
|
|
7858
7963
|
// src/services/distributed/shared.ts
|
|
7859
7964
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7860
|
-
import { dirname as
|
|
7965
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
7861
7966
|
import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
|
|
7862
7967
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7863
7968
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -7873,7 +7978,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7873
7978
|
}
|
|
7874
7979
|
function resolvePlanAudioPath(planDir) {
|
|
7875
7980
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7876
|
-
const candidate =
|
|
7981
|
+
const candidate = join17(planDir, name);
|
|
7877
7982
|
if (existsSync14(candidate)) return candidate;
|
|
7878
7983
|
}
|
|
7879
7984
|
return null;
|
|
@@ -8131,10 +8236,10 @@ function buildSyntheticRenderJob(input) {
|
|
|
8131
8236
|
var cachedProducerVersion = null;
|
|
8132
8237
|
function readProducerVersion() {
|
|
8133
8238
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
8134
|
-
const startDir =
|
|
8239
|
+
const startDir = dirname11(fileURLToPath3(import.meta.url));
|
|
8135
8240
|
let current = startDir;
|
|
8136
8241
|
for (let i = 0; i < 10; i++) {
|
|
8137
|
-
const candidate =
|
|
8242
|
+
const candidate = join17(current, "package.json");
|
|
8138
8243
|
if (existsSync14(candidate)) {
|
|
8139
8244
|
try {
|
|
8140
8245
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
@@ -8145,7 +8250,7 @@ function readProducerVersion() {
|
|
|
8145
8250
|
} catch {
|
|
8146
8251
|
}
|
|
8147
8252
|
}
|
|
8148
|
-
const parent =
|
|
8253
|
+
const parent = dirname11(current);
|
|
8149
8254
|
if (parent === current) break;
|
|
8150
8255
|
current = parent;
|
|
8151
8256
|
}
|
|
@@ -8170,11 +8275,11 @@ var RenderChunkValidationError = class extends Error {
|
|
|
8170
8275
|
};
|
|
8171
8276
|
function createChunkVideoFrameInjectorFactory(frameLookup, options) {
|
|
8172
8277
|
if (!(frameLookup instanceof Promise))
|
|
8173
|
-
return () =>
|
|
8278
|
+
return () => createVideoFrameInjector3(frameLookup, options);
|
|
8174
8279
|
return () => {
|
|
8175
8280
|
let hook;
|
|
8176
8281
|
return async (page, time, heldVideoTime) => {
|
|
8177
|
-
hook ??=
|
|
8282
|
+
hook ??= createVideoFrameInjector3(await frameLookup, options);
|
|
8178
8283
|
await hook?.(page, time, heldVideoTime);
|
|
8179
8284
|
};
|
|
8180
8285
|
};
|
|
@@ -8188,7 +8293,7 @@ function frameNumberFromFileName(name) {
|
|
|
8188
8293
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
8189
8294
|
const result = [];
|
|
8190
8295
|
for (const v of videos) {
|
|
8191
|
-
const outputDir =
|
|
8296
|
+
const outputDir = join18(planDir, "video-frames", v.videoId);
|
|
8192
8297
|
if (!existsSync15(outputDir)) {
|
|
8193
8298
|
throw new Error(
|
|
8194
8299
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -8207,7 +8312,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8207
8312
|
if (!frameName) continue;
|
|
8208
8313
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
8209
8314
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
8210
|
-
framePaths.set(frameIndex,
|
|
8315
|
+
framePaths.set(frameIndex, join18(outputDir, frameName));
|
|
8211
8316
|
}
|
|
8212
8317
|
result.push({
|
|
8213
8318
|
videoId: v.videoId,
|
|
@@ -8224,15 +8329,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8224
8329
|
}
|
|
8225
8330
|
return result;
|
|
8226
8331
|
}
|
|
8227
|
-
function contiguousRuns(sortedIndexes) {
|
|
8228
|
-
const runs2 = [];
|
|
8229
|
-
for (const index of sortedIndexes) {
|
|
8230
|
-
const last = runs2[runs2.length - 1];
|
|
8231
|
-
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
8232
|
-
else runs2.push({ firstFrame: index, frames: 1 });
|
|
8233
|
-
}
|
|
8234
|
-
return runs2;
|
|
8235
|
-
}
|
|
8236
8332
|
function buildChunkCaptureOptions(input) {
|
|
8237
8333
|
const { dimensions, encoder } = input;
|
|
8238
8334
|
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
|
|
@@ -8255,28 +8351,19 @@ function buildChunkCaptureOptions(input) {
|
|
|
8255
8351
|
requiresWebGpu: compositionRequiresWebGpu3(input.html)
|
|
8256
8352
|
};
|
|
8257
8353
|
}
|
|
8258
|
-
function
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8354
|
+
function planExtractedFrames(planVideos) {
|
|
8355
|
+
return planVideos.extracted.map((video) => ({
|
|
8356
|
+
...video,
|
|
8357
|
+
outputDir: "",
|
|
8358
|
+
framePaths: /* @__PURE__ */ new Map()
|
|
8359
|
+
}));
|
|
8360
|
+
}
|
|
8361
|
+
function shownPlanFrameIndexes(planVideos, frames, fps) {
|
|
8362
|
+
return shownDeferredFrameIndexes(
|
|
8363
|
+
{ videos: planVideos.videos, extracted: planExtractedFrames(planVideos) },
|
|
8364
|
+
frames,
|
|
8365
|
+
fps
|
|
8270
8366
|
);
|
|
8271
|
-
for (const frame of frames) {
|
|
8272
|
-
const globalTime = frame * fps.den / fps.num;
|
|
8273
|
-
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
8274
|
-
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
8275
|
-
indexes.add(payload.frameIndex);
|
|
8276
|
-
shown.set(videoId, indexes);
|
|
8277
|
-
}
|
|
8278
|
-
}
|
|
8279
|
-
return shown;
|
|
8280
8367
|
}
|
|
8281
8368
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
8282
8369
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
@@ -8293,49 +8380,32 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8293
8380
|
url: remoteSources[path].split("?")[0]
|
|
8294
8381
|
});
|
|
8295
8382
|
}
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8383
|
+
const planned = new Map(input.planVideos.extracted.map((video) => [video.videoId, video]));
|
|
8384
|
+
return extractDeferredFrames({
|
|
8385
|
+
videos: input.planVideos.videos,
|
|
8386
|
+
extracted: planExtractedFrames(input.planVideos),
|
|
8387
|
+
frames: input.frames,
|
|
8388
|
+
fps: input.fps,
|
|
8389
|
+
outputRoot: input.outputRoot,
|
|
8390
|
+
cfg: input.cfg,
|
|
8391
|
+
shownBoxes: input.shownBoxes,
|
|
8392
|
+
sourceOf: (video) => {
|
|
8393
|
+
const range = planned.get(video.videoId).deferredRange;
|
|
8306
8394
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8307
|
-
const sourcePath = remoteUrl ??
|
|
8395
|
+
const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
|
|
8308
8396
|
if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
|
|
8309
8397
|
throw new RenderChunkValidationError(
|
|
8310
8398
|
MISSING_PLAN_ARTIFACT,
|
|
8311
8399
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
8312
8400
|
);
|
|
8313
8401
|
}
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
outputDir: input.outputRoot,
|
|
8322
|
-
format: range.format,
|
|
8323
|
-
frameRanges: contiguousRuns(indexes),
|
|
8324
|
-
shownBox: input.shownBoxes?.get(video.videoId)
|
|
8325
|
-
},
|
|
8326
|
-
void 0,
|
|
8327
|
-
input.cfg
|
|
8328
|
-
);
|
|
8329
|
-
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
8330
|
-
if (missing !== void 0) {
|
|
8331
|
-
throw new RenderChunkValidationError(
|
|
8332
|
-
INVALID_VIDEO_METADATA,
|
|
8333
|
-
`[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
|
|
8334
|
-
);
|
|
8335
|
-
}
|
|
8336
|
-
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
8337
|
-
})
|
|
8338
|
-
);
|
|
8402
|
+
return sourcePath;
|
|
8403
|
+
},
|
|
8404
|
+
onMissingFrame: (video, index) => new RenderChunkValidationError(
|
|
8405
|
+
INVALID_VIDEO_METADATA,
|
|
8406
|
+
`[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${index}; the plan expected ${video.totalFrames} frames from ${planned.get(video.videoId).deferredRange.sourcePath}.`
|
|
8407
|
+
)
|
|
8408
|
+
});
|
|
8339
8409
|
}
|
|
8340
8410
|
|
|
8341
8411
|
// src/services/distributed/planFrameCost.ts
|
|
@@ -8353,12 +8423,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8353
8423
|
planVideos,
|
|
8354
8424
|
frames: sampled,
|
|
8355
8425
|
fps: fpsRatio,
|
|
8356
|
-
outputRoot:
|
|
8426
|
+
outputRoot: join19(workDir, "frame-cost-video-frames"),
|
|
8357
8427
|
cfg: input.cfg
|
|
8358
8428
|
})
|
|
8359
8429
|
];
|
|
8360
|
-
const lookup = extracted.length > 0 ?
|
|
8361
|
-
const compiledDir =
|
|
8430
|
+
const lookup = extracted.length > 0 ? createFrameLookupTable4(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
8431
|
+
const compiledDir = join19(planDir, "compiled");
|
|
8362
8432
|
const fileServer = await createFileServer({
|
|
8363
8433
|
projectDir: compiledDir,
|
|
8364
8434
|
compiledDir,
|
|
@@ -8370,7 +8440,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8370
8440
|
try {
|
|
8371
8441
|
session = await createCaptureSession9(
|
|
8372
8442
|
fileServer.url,
|
|
8373
|
-
|
|
8443
|
+
join19(workDir, "frame-cost"),
|
|
8374
8444
|
buildChunkCaptureOptions({
|
|
8375
8445
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
8376
8446
|
encoder: input,
|
|
@@ -8467,7 +8537,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
8467
8537
|
}
|
|
8468
8538
|
|
|
8469
8539
|
// src/services/render/planReferenceBackground.ts
|
|
8470
|
-
import { join as
|
|
8540
|
+
import { join as join20 } from "node:path";
|
|
8471
8541
|
import {
|
|
8472
8542
|
closeCaptureSession as closeCaptureSession10,
|
|
8473
8543
|
createCaptureSession as createCaptureSession10,
|
|
@@ -8480,14 +8550,14 @@ async function readPlanReferenceBackground(input) {
|
|
|
8480
8550
|
try {
|
|
8481
8551
|
server = await createFileServer({
|
|
8482
8552
|
projectDir: input.projectDir,
|
|
8483
|
-
compiledDir:
|
|
8553
|
+
compiledDir: join20(input.workDir, "compiled"),
|
|
8484
8554
|
port: 0,
|
|
8485
8555
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8486
8556
|
fps: input.options.fps
|
|
8487
8557
|
});
|
|
8488
8558
|
session = await createCaptureSession10(
|
|
8489
8559
|
server.url,
|
|
8490
|
-
|
|
8560
|
+
join20(input.workDir, "reference"),
|
|
8491
8561
|
{
|
|
8492
8562
|
...input.options,
|
|
8493
8563
|
requiresWebGpu: false
|
|
@@ -8524,7 +8594,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8524
8594
|
|
|
8525
8595
|
// src/services/render/stages/freezePlan.ts
|
|
8526
8596
|
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8527
|
-
import { join as
|
|
8597
|
+
import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8528
8598
|
import {
|
|
8529
8599
|
REFERENCE_BACKGROUND_FIELD,
|
|
8530
8600
|
referenceBackgroundResult as referenceBackgroundResult3
|
|
@@ -8742,7 +8812,7 @@ function listPlanFiles(planDir) {
|
|
|
8742
8812
|
function walk(dir) {
|
|
8743
8813
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
8744
8814
|
for (const entry of entries) {
|
|
8745
|
-
const full =
|
|
8815
|
+
const full = join21(dir, entry.name);
|
|
8746
8816
|
if (entry.isDirectory()) {
|
|
8747
8817
|
walk(full);
|
|
8748
8818
|
} else if (entry.isFile()) {
|
|
@@ -8783,15 +8853,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8783
8853
|
function writePlanRemoteFiles(planDir, files) {
|
|
8784
8854
|
if (files.length === 0) return;
|
|
8785
8855
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8786
|
-
writeFileSync6(
|
|
8856
|
+
writeFileSync6(join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8787
8857
|
}
|
|
8788
8858
|
function readPlanRemoteFiles(planDir) {
|
|
8789
|
-
const path =
|
|
8859
|
+
const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8790
8860
|
return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8791
8861
|
}
|
|
8792
8862
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8793
|
-
const planJsonPath =
|
|
8794
|
-
const encoderJsonPath =
|
|
8863
|
+
const planJsonPath = join21(planDir, "plan.json");
|
|
8864
|
+
const encoderJsonPath = join21(planDir, "meta", "encoder.json");
|
|
8795
8865
|
if (!existsSync16(planJsonPath)) {
|
|
8796
8866
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8797
8867
|
}
|
|
@@ -8828,18 +8898,18 @@ async function freezePlan(input) {
|
|
|
8828
8898
|
if (!existsSync16(planDir)) {
|
|
8829
8899
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8830
8900
|
}
|
|
8831
|
-
const metaDir =
|
|
8901
|
+
const metaDir = join21(planDir, "meta");
|
|
8832
8902
|
if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8833
8903
|
writeFileSync6(
|
|
8834
|
-
|
|
8904
|
+
join21(metaDir, "composition.json"),
|
|
8835
8905
|
`${JSON.stringify(composition, null, 2)}
|
|
8836
8906
|
`,
|
|
8837
8907
|
"utf-8"
|
|
8838
8908
|
);
|
|
8839
8909
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8840
8910
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8841
|
-
writeFileSync6(
|
|
8842
|
-
writeFileSync6(
|
|
8911
|
+
writeFileSync6(join21(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8912
|
+
writeFileSync6(join21(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8843
8913
|
`, "utf-8");
|
|
8844
8914
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8845
8915
|
const planHash = computePlanHash({
|
|
@@ -8864,7 +8934,7 @@ async function freezePlan(input) {
|
|
|
8864
8934
|
duration: durationSeconds,
|
|
8865
8935
|
hasAudio
|
|
8866
8936
|
};
|
|
8867
|
-
const planJsonPath =
|
|
8937
|
+
const planJsonPath = join21(planDir, "plan.json");
|
|
8868
8938
|
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8869
8939
|
`, "utf-8");
|
|
8870
8940
|
return { planJsonPath, planHash };
|
|
@@ -8908,14 +8978,14 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8908
8978
|
// src/services/distributed/chunkAudio.ts
|
|
8909
8979
|
import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8910
8980
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8911
|
-
import { join as
|
|
8981
|
+
import { join as join23 } from "node:path";
|
|
8912
8982
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8913
8983
|
|
|
8914
8984
|
// src/services/distributed/mp4Stitch.ts
|
|
8915
8985
|
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
|
|
8916
8986
|
import { open } from "node:fs/promises";
|
|
8917
8987
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
8918
|
-
import { dirname as
|
|
8988
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
8919
8989
|
import { finished, pipeline } from "node:stream/promises";
|
|
8920
8990
|
function boxAt(src, off, at, limit) {
|
|
8921
8991
|
let size = src.readUInt32BE(off);
|
|
@@ -9279,15 +9349,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
9279
9349
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
9280
9350
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
9281
9351
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
9282
|
-
mkdirSync8(
|
|
9352
|
+
mkdirSync8(dirname12(outputPath), { recursive: true });
|
|
9283
9353
|
const workDir = mkdtempSync2(`${outputPath}.stitch-`);
|
|
9284
|
-
const out = createWriteStream(
|
|
9354
|
+
const out = createWriteStream(join22(workDir, "out.mp4"));
|
|
9285
9355
|
try {
|
|
9286
9356
|
out.write(header);
|
|
9287
9357
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
9288
9358
|
out.end();
|
|
9289
9359
|
await finished(out);
|
|
9290
|
-
renameWithRetry7(
|
|
9360
|
+
renameWithRetry7(join22(workDir, "out.mp4"), outputPath);
|
|
9291
9361
|
} finally {
|
|
9292
9362
|
out.destroy();
|
|
9293
9363
|
rmSync6(workDir, { recursive: true, force: true });
|
|
@@ -9381,7 +9451,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9381
9451
|
});
|
|
9382
9452
|
const bytes = readFileSync8(input.audioPath);
|
|
9383
9453
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
9384
|
-
mkdirSync9(
|
|
9454
|
+
mkdirSync9(join23(input.planDir, SLICE_DIR), { recursive: true });
|
|
9385
9455
|
input.chunks.forEach((_, index) => {
|
|
9386
9456
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
9387
9457
|
if (!cut) return;
|
|
@@ -9389,17 +9459,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
9389
9459
|
const start = offsets[cut.firstPacket];
|
|
9390
9460
|
layout.partStarts[index] = start;
|
|
9391
9461
|
const end = offsets[last] + track.sizes[last];
|
|
9392
|
-
writeFileSync7(
|
|
9462
|
+
writeFileSync7(join23(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
9393
9463
|
});
|
|
9394
9464
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
9395
|
-
writeFileSync7(
|
|
9396
|
-
writeFileSync7(
|
|
9465
|
+
writeFileSync7(join23(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
9466
|
+
writeFileSync7(join23(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
9397
9467
|
}
|
|
9398
9468
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9399
|
-
const layoutPath =
|
|
9469
|
+
const layoutPath = join23(planDir, SLICE_LAYOUT);
|
|
9400
9470
|
if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9401
9471
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9402
|
-
const frame = readFileSync8(
|
|
9472
|
+
const frame = readFileSync8(join23(planDir, SLICE_FRAME));
|
|
9403
9473
|
const fd = openSync4(scratchPath, "w");
|
|
9404
9474
|
try {
|
|
9405
9475
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -9412,7 +9482,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
9412
9482
|
);
|
|
9413
9483
|
const start = layout.partStarts[chunkIndex];
|
|
9414
9484
|
if (start !== void 0) {
|
|
9415
|
-
const part = readFileSync8(
|
|
9485
|
+
const part = readFileSync8(join23(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
9416
9486
|
writeSync(fd, part, 0, part.length, start);
|
|
9417
9487
|
}
|
|
9418
9488
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -9468,7 +9538,7 @@ async function muxChunkWithAudio(input) {
|
|
|
9468
9538
|
// src/services/distributed/planSize.ts
|
|
9469
9539
|
import { createHash as createHash7 } from "node:crypto";
|
|
9470
9540
|
import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
|
|
9471
|
-
import { extname as extname3, join as
|
|
9541
|
+
import { extname as extname3, join as join24, relative as relative4 } from "node:path";
|
|
9472
9542
|
var MAX_TOP_COMPONENTS = 10;
|
|
9473
9543
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9474
9544
|
".aac",
|
|
@@ -9580,7 +9650,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
9580
9650
|
return;
|
|
9581
9651
|
}
|
|
9582
9652
|
for (const entry of entries) {
|
|
9583
|
-
const full =
|
|
9653
|
+
const full = join24(dir, entry.name);
|
|
9584
9654
|
if (entry.isDirectory()) {
|
|
9585
9655
|
walkRegularFiles(full, visit);
|
|
9586
9656
|
} else if (entry.isFile()) {
|
|
@@ -9796,13 +9866,13 @@ function assertPositiveInteger(name, value) {
|
|
|
9796
9866
|
}
|
|
9797
9867
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
9798
9868
|
"plan.json",
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9869
|
+
join25("meta", "composition.json"),
|
|
9870
|
+
join25("meta", "encoder.json"),
|
|
9871
|
+
join25("meta", "chunks.json")
|
|
9802
9872
|
];
|
|
9803
9873
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
9804
9874
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
9805
|
-
rmSync7(
|
|
9875
|
+
rmSync7(join25(planDir, relativePath), { force: true });
|
|
9806
9876
|
}
|
|
9807
9877
|
}
|
|
9808
9878
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -9957,19 +10027,19 @@ function createVideoSourceShipper(input) {
|
|
|
9957
10027
|
const known = shipped.get(srcPath);
|
|
9958
10028
|
if (known) return known;
|
|
9959
10029
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
9960
|
-
const current = stagedRel === null ? srcPath :
|
|
10030
|
+
const current = stagedRel === null ? srcPath : join25(input.planDir, "video-frames", stagedRel);
|
|
9961
10031
|
let result = null;
|
|
9962
10032
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9963
10033
|
const rel = pathInside(root, srcPath);
|
|
9964
|
-
if (rel !== null && (existsSync18(
|
|
10034
|
+
if (rel !== null && (existsSync18(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9965
10035
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9966
10036
|
break;
|
|
9967
10037
|
}
|
|
9968
10038
|
}
|
|
9969
10039
|
if (result === null) {
|
|
9970
10040
|
result = `video-sources/${shipped.size}${extname4(current)}`;
|
|
9971
|
-
const destination =
|
|
9972
|
-
mkdirSync10(
|
|
10041
|
+
const destination = join25(input.planDir, ...result.split("/"));
|
|
10042
|
+
mkdirSync10(join25(input.planDir, "video-sources"), { recursive: true });
|
|
9973
10043
|
try {
|
|
9974
10044
|
linkSync(current, destination);
|
|
9975
10045
|
} catch {
|
|
@@ -10018,14 +10088,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10018
10088
|
variables: config.variables
|
|
10019
10089
|
});
|
|
10020
10090
|
const entryFile = config.entryFile ?? "index.html";
|
|
10021
|
-
const htmlPath =
|
|
10091
|
+
const htmlPath = join25(projectDir, entryFile);
|
|
10022
10092
|
if (!existsSync18(htmlPath)) {
|
|
10023
10093
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
10024
10094
|
}
|
|
10025
|
-
const workDir =
|
|
10095
|
+
const workDir = join25(planDir, ".plan-work");
|
|
10026
10096
|
rmSync7(workDir, { recursive: true, force: true });
|
|
10027
10097
|
mkdirSync10(workDir, { recursive: true });
|
|
10028
|
-
const compiledDir =
|
|
10098
|
+
const compiledDir = join25(workDir, "compiled");
|
|
10029
10099
|
mkdirSync10(compiledDir, { recursive: true });
|
|
10030
10100
|
let stageStarted = Date.now();
|
|
10031
10101
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -10039,10 +10109,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10039
10109
|
}
|
|
10040
10110
|
});
|
|
10041
10111
|
logPlanStage(log, "copy", stageStarted);
|
|
10042
|
-
const finalCompiledDir =
|
|
10112
|
+
const finalCompiledDir = join25(planDir, "compiled");
|
|
10043
10113
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
10044
10114
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
10045
|
-
const remoteLocalPath = (rel) =>
|
|
10115
|
+
const remoteLocalPath = (rel) => join25(projectDir, ...rel.split("/"));
|
|
10046
10116
|
for (const [, file] of remoteFiles) {
|
|
10047
10117
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
10048
10118
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -10245,10 +10315,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10245
10315
|
let stageStarted = Date.now();
|
|
10246
10316
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
10247
10317
|
logPlanStage(log, "extract", stageStarted);
|
|
10248
|
-
const stagedVideoFrames =
|
|
10249
|
-
const videoFramesDst =
|
|
10318
|
+
const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
|
|
10319
|
+
const videoFramesDst = join25(planDir, "video-frames");
|
|
10250
10320
|
if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10251
|
-
const videoSourcesDir =
|
|
10321
|
+
const videoSourcesDir = join25(planDir, "video-sources");
|
|
10252
10322
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10253
10323
|
if (existsSync18(stagedVideoFrames)) {
|
|
10254
10324
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
@@ -10285,7 +10355,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10285
10355
|
};
|
|
10286
10356
|
})
|
|
10287
10357
|
});
|
|
10288
|
-
mkdirSync10(
|
|
10358
|
+
mkdirSync10(join25(planDir, "meta"), { recursive: true });
|
|
10289
10359
|
writePlanRemoteFiles(
|
|
10290
10360
|
planDir,
|
|
10291
10361
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -10295,7 +10365,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10295
10365
|
}))
|
|
10296
10366
|
);
|
|
10297
10367
|
writeFileSync8(
|
|
10298
|
-
|
|
10368
|
+
join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
10299
10369
|
JSON.stringify(planVideosJson, null, 2),
|
|
10300
10370
|
"utf-8"
|
|
10301
10371
|
);
|
|
@@ -10374,7 +10444,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10374
10444
|
if (audioResult?.audioError) {
|
|
10375
10445
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10376
10446
|
}
|
|
10377
|
-
const planAudioPath =
|
|
10447
|
+
const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10378
10448
|
if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
|
|
10379
10449
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10380
10450
|
}
|
|
@@ -10517,9 +10587,9 @@ import {
|
|
|
10517
10587
|
writeFileSync as writeFileSync10
|
|
10518
10588
|
} from "node:fs";
|
|
10519
10589
|
import { tmpdir } from "node:os";
|
|
10520
|
-
import { dirname as
|
|
10590
|
+
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
10521
10591
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
10522
|
-
import { createFrameLookupTable as
|
|
10592
|
+
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
10523
10593
|
|
|
10524
10594
|
// src/services/distributed/planV2Publisher.ts
|
|
10525
10595
|
import {
|
|
@@ -10533,7 +10603,7 @@ import {
|
|
|
10533
10603
|
writeFileSync as writeFileSync9
|
|
10534
10604
|
} from "node:fs";
|
|
10535
10605
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
10536
|
-
import { dirname as
|
|
10606
|
+
import { dirname as dirname13, join as join27 } from "node:path";
|
|
10537
10607
|
|
|
10538
10608
|
// src/services/distributed/planV2Errors.ts
|
|
10539
10609
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -10548,7 +10618,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
10548
10618
|
};
|
|
10549
10619
|
|
|
10550
10620
|
// src/services/distributed/planV2Layout.ts
|
|
10551
|
-
import { join as
|
|
10621
|
+
import { join as join26 } from "node:path";
|
|
10552
10622
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
10553
10623
|
function assertPlanV2Sha256(value, field) {
|
|
10554
10624
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -10558,7 +10628,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
10558
10628
|
}
|
|
10559
10629
|
function planV2BlobPath(root, sha256) {
|
|
10560
10630
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
10561
|
-
return
|
|
10631
|
+
return join26(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
10562
10632
|
}
|
|
10563
10633
|
|
|
10564
10634
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -10598,8 +10668,8 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10598
10668
|
}
|
|
10599
10669
|
this.destinationDir = destinationDir;
|
|
10600
10670
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
10601
|
-
mkdirSync11(
|
|
10602
|
-
this.temporaryDir = mkdtempSync3(
|
|
10671
|
+
mkdirSync11(dirname13(destinationDir), { recursive: true });
|
|
10672
|
+
this.temporaryDir = mkdtempSync3(join27(dirname13(destinationDir), ".plan-v2-publish-"));
|
|
10603
10673
|
}
|
|
10604
10674
|
async putBlob(blob) {
|
|
10605
10675
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -10611,9 +10681,9 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10611
10681
|
}
|
|
10612
10682
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10613
10683
|
if (existsSync19(destinationPath)) return;
|
|
10614
|
-
mkdirSync11(
|
|
10615
|
-
const stagingDir = mkdtempSync3(
|
|
10616
|
-
const temporaryPath =
|
|
10684
|
+
mkdirSync11(dirname13(destinationPath), { recursive: true });
|
|
10685
|
+
const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10686
|
+
const temporaryPath = join27(stagingDir, "blob");
|
|
10617
10687
|
try {
|
|
10618
10688
|
try {
|
|
10619
10689
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -10634,7 +10704,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10634
10704
|
);
|
|
10635
10705
|
}
|
|
10636
10706
|
}
|
|
10637
|
-
writeFileSync9(
|
|
10707
|
+
writeFileSync9(join27(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10638
10708
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
10639
10709
|
this.#committed = true;
|
|
10640
10710
|
}
|
|
@@ -10679,7 +10749,7 @@ function listFiles(root) {
|
|
|
10679
10749
|
const rootResolved = resolve9(root);
|
|
10680
10750
|
function walk(dir) {
|
|
10681
10751
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
10682
|
-
const absolutePath =
|
|
10752
|
+
const absolutePath = join28(dir, entry.name);
|
|
10683
10753
|
if (entry.isDirectory()) {
|
|
10684
10754
|
walk(absolutePath);
|
|
10685
10755
|
} else if (entry.isFile()) {
|
|
@@ -10706,13 +10776,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10706
10776
|
}
|
|
10707
10777
|
}
|
|
10708
10778
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10709
|
-
const videoRoot =
|
|
10779
|
+
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10710
10780
|
if (!existsSync20(videoRoot)) return;
|
|
10711
10781
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10712
10782
|
if (!videoEntry.isDirectory()) continue;
|
|
10713
|
-
const videoDir =
|
|
10783
|
+
const videoDir = join28(videoRoot, videoEntry.name);
|
|
10714
10784
|
if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
10715
|
-
const sentinelPath =
|
|
10785
|
+
const sentinelPath = join28(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
10716
10786
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
10717
10787
|
}
|
|
10718
10788
|
}
|
|
@@ -10759,7 +10829,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10759
10829
|
if (video.deferredRange) {
|
|
10760
10830
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
10761
10831
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
10762
|
-
framePaths2.set(index,
|
|
10832
|
+
framePaths2.set(index, join28(outputDir, String(index)));
|
|
10763
10833
|
}
|
|
10764
10834
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
10765
10835
|
}
|
|
@@ -10767,7 +10837,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10767
10837
|
const framePaths = /* @__PURE__ */ new Map();
|
|
10768
10838
|
for (const frameName of frameNames) {
|
|
10769
10839
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
10770
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
10840
|
+
assertValidExtractionCacheCompleteSentinel(join28(outputDir, frameName));
|
|
10771
10841
|
continue;
|
|
10772
10842
|
}
|
|
10773
10843
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -10786,7 +10856,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10786
10856
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
10787
10857
|
);
|
|
10788
10858
|
}
|
|
10789
|
-
framePaths.set(frameIndex,
|
|
10859
|
+
framePaths.set(frameIndex, join28(outputDir, frameName));
|
|
10790
10860
|
}
|
|
10791
10861
|
return {
|
|
10792
10862
|
...video,
|
|
@@ -10807,7 +10877,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10807
10877
|
}
|
|
10808
10878
|
}
|
|
10809
10879
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10810
|
-
const videosPath =
|
|
10880
|
+
const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10811
10881
|
if (!existsSync20(videosPath)) return;
|
|
10812
10882
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10813
10883
|
for (const video of videos.extracted) {
|
|
@@ -10841,13 +10911,13 @@ function parseChunkSlices(value) {
|
|
|
10841
10911
|
});
|
|
10842
10912
|
}
|
|
10843
10913
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10844
|
-
const videoRoot =
|
|
10914
|
+
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10845
10915
|
const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10846
|
-
const videosPath =
|
|
10916
|
+
const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10847
10917
|
if (!existsSync20(videosPath)) {
|
|
10848
10918
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10849
10919
|
}
|
|
10850
|
-
const chunksPath =
|
|
10920
|
+
const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
|
|
10851
10921
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10852
10922
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
10853
10923
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -10855,7 +10925,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
|
10855
10925
|
const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
|
|
10856
10926
|
const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
|
|
10857
10927
|
const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
|
|
10858
|
-
const table =
|
|
10928
|
+
const table = createFrameLookupTable5(
|
|
10859
10929
|
parsedVideos.videos,
|
|
10860
10930
|
extracted,
|
|
10861
10931
|
resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
|
|
@@ -10902,9 +10972,9 @@ function computeManifestHash(manifest) {
|
|
|
10902
10972
|
}
|
|
10903
10973
|
function writeBlob(sourcePath, destinationPath) {
|
|
10904
10974
|
if (existsSync20(destinationPath)) return;
|
|
10905
|
-
mkdirSync12(
|
|
10906
|
-
const temporaryDir = mkdtempSync4(
|
|
10907
|
-
const temporaryPath =
|
|
10975
|
+
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
10976
|
+
const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
10977
|
+
const temporaryPath = join28(temporaryDir, "blob");
|
|
10908
10978
|
try {
|
|
10909
10979
|
copyFileSync8(sourcePath, temporaryPath);
|
|
10910
10980
|
renameWithRetry10(temporaryPath, destinationPath);
|
|
@@ -10940,7 +11010,7 @@ async function putTasks(tasks) {
|
|
|
10940
11010
|
if (failure) throw failure.reason;
|
|
10941
11011
|
}
|
|
10942
11012
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10943
|
-
const executionPlanPath =
|
|
11013
|
+
const executionPlanPath = join28(executionPlanDir, "plan.json");
|
|
10944
11014
|
if (!existsSync20(executionPlanPath)) {
|
|
10945
11015
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10946
11016
|
}
|
|
@@ -10968,7 +11038,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10968
11038
|
}
|
|
10969
11039
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
10970
11040
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
10971
|
-
const encoder = readJsonFile(
|
|
11041
|
+
const encoder = readJsonFile(join28(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
10972
11042
|
const files = listFiles(executionPlanDir);
|
|
10973
11043
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
10974
11044
|
for (const file of files) {
|
|
@@ -11028,14 +11098,14 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
11028
11098
|
if (publication.storedBlobs.length > 0) {
|
|
11029
11099
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
11030
11100
|
}
|
|
11031
|
-
mkdirSync12(
|
|
11032
|
-
const tempDir = mkdtempSync4(
|
|
11101
|
+
mkdirSync12(dirname14(planV2Dir), { recursive: true });
|
|
11102
|
+
const tempDir = mkdtempSync4(join28(dirname14(planV2Dir), ".plan-v2-build-"));
|
|
11033
11103
|
try {
|
|
11034
11104
|
for (const blob of publication.blobs) {
|
|
11035
11105
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
11036
11106
|
}
|
|
11037
11107
|
writeFileSync10(
|
|
11038
|
-
|
|
11108
|
+
join28(tempDir, "plan.json"),
|
|
11039
11109
|
canonicalJsonStringify(publication.manifest),
|
|
11040
11110
|
"utf-8"
|
|
11041
11111
|
);
|
|
@@ -11076,7 +11146,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
|
|
|
11076
11146
|
}
|
|
11077
11147
|
}
|
|
11078
11148
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
11079
|
-
const stagingRoot = mkdtempSync4(
|
|
11149
|
+
const stagingRoot = mkdtempSync4(join28(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
11080
11150
|
const digests = /* @__PURE__ */ new Map();
|
|
11081
11151
|
try {
|
|
11082
11152
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -11087,7 +11157,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11087
11157
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
11088
11158
|
whileAudioMixes: () => putBlobs(
|
|
11089
11159
|
publisher,
|
|
11090
|
-
listFiles(
|
|
11160
|
+
listFiles(join28(stagingRoot, "compiled")).map(
|
|
11091
11161
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
11092
11162
|
)
|
|
11093
11163
|
),
|
|
@@ -11112,7 +11182,7 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
11112
11182
|
}
|
|
11113
11183
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
11114
11184
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
11115
|
-
stagingParentDir:
|
|
11185
|
+
stagingParentDir: dirname14(planV2Dir)
|
|
11116
11186
|
});
|
|
11117
11187
|
return resultFromManifest(planV2Dir, manifest);
|
|
11118
11188
|
}
|
|
@@ -11124,7 +11194,7 @@ function resultFromManifest(planV2Dir, manifest) {
|
|
|
11124
11194
|
isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
|
|
11125
11195
|
),
|
|
11126
11196
|
planDir: planV2Dir,
|
|
11127
|
-
manifestPath:
|
|
11197
|
+
manifestPath: join28(planV2Dir, "plan.json"),
|
|
11128
11198
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
11129
11199
|
planHash: manifest.planHash,
|
|
11130
11200
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -11260,7 +11330,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11260
11330
|
return parsed;
|
|
11261
11331
|
}
|
|
11262
11332
|
function readPlanV2Manifest(planV2Dir) {
|
|
11263
|
-
const manifestPath =
|
|
11333
|
+
const manifestPath = join28(planV2Dir, "plan.json");
|
|
11264
11334
|
if (!existsSync20(manifestPath)) {
|
|
11265
11335
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11266
11336
|
}
|
|
@@ -11305,17 +11375,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11305
11375
|
artifact,
|
|
11306
11376
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
11307
11377
|
}));
|
|
11308
|
-
mkdirSync12(
|
|
11309
|
-
const tempDir = mkdtempSync4(
|
|
11378
|
+
mkdirSync12(dirname14(destinationDir), { recursive: true });
|
|
11379
|
+
const tempDir = mkdtempSync4(join28(dirname14(destinationDir), ".plan-v2-materialize-"));
|
|
11310
11380
|
try {
|
|
11311
11381
|
for (const { artifact, sourcePath } of verified) {
|
|
11312
|
-
const destinationPath =
|
|
11313
|
-
mkdirSync12(
|
|
11382
|
+
const destinationPath = join28(tempDir, ...artifact.path.split("/"));
|
|
11383
|
+
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
11314
11384
|
copyFileSync8(sourcePath, destinationPath);
|
|
11315
11385
|
}
|
|
11316
11386
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
11317
11387
|
writeFileSync10(
|
|
11318
|
-
|
|
11388
|
+
join28(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
11319
11389
|
canonicalJsonStringify({
|
|
11320
11390
|
manifest,
|
|
11321
11391
|
target,
|
|
@@ -11338,7 +11408,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11338
11408
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
11339
11409
|
// before the container change carries the legacy name, and materializing it
|
|
11340
11410
|
// under the new one would point at a file that was never written.
|
|
11341
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
11411
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join28(destinationDir, audioArtifact.path) : null : null
|
|
11342
11412
|
};
|
|
11343
11413
|
}
|
|
11344
11414
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -11357,7 +11427,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11357
11427
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
11358
11428
|
}
|
|
11359
11429
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11360
|
-
const markerPath =
|
|
11430
|
+
const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11361
11431
|
if (!existsSync20(markerPath)) return null;
|
|
11362
11432
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11363
11433
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -11375,7 +11445,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11375
11445
|
assertRemoteSources(remoteSources, required);
|
|
11376
11446
|
for (const artifact of required) {
|
|
11377
11447
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11378
|
-
const path =
|
|
11448
|
+
const path = join28(planDir, ...artifact.path.split("/"));
|
|
11379
11449
|
if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11380
11450
|
throw new PlanV2IntegrityError(
|
|
11381
11451
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
@@ -11391,7 +11461,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11391
11461
|
// src/services/distributed/planV2Execution.ts
|
|
11392
11462
|
import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
|
|
11393
11463
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11394
|
-
import { join as
|
|
11464
|
+
import { join as join31 } from "node:path";
|
|
11395
11465
|
|
|
11396
11466
|
// src/services/distributed/assemble.ts
|
|
11397
11467
|
import {
|
|
@@ -11409,7 +11479,7 @@ import {
|
|
|
11409
11479
|
statSync as statSync8,
|
|
11410
11480
|
writeFileSync as writeFileSync11
|
|
11411
11481
|
} from "node:fs";
|
|
11412
|
-
import { dirname as
|
|
11482
|
+
import { dirname as dirname15, join as join29 } from "node:path";
|
|
11413
11483
|
import {
|
|
11414
11484
|
appendRateCapArgs as appendRateCapArgs2,
|
|
11415
11485
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
@@ -11455,8 +11525,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11455
11525
|
}
|
|
11456
11526
|
const format = plan.dimensions.format;
|
|
11457
11527
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11458
|
-
if (!existsSync21(
|
|
11459
|
-
mkdirSync13(
|
|
11528
|
+
if (!existsSync21(dirname15(outputPath))) {
|
|
11529
|
+
mkdirSync13(dirname15(outputPath), { recursive: true });
|
|
11460
11530
|
}
|
|
11461
11531
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
11462
11532
|
const fps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
@@ -11478,7 +11548,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11478
11548
|
};
|
|
11479
11549
|
try {
|
|
11480
11550
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11481
|
-
const finalPath =
|
|
11551
|
+
const finalPath = join29(workDir, `output.${format}`);
|
|
11482
11552
|
const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
|
|
11483
11553
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11484
11554
|
renameWithRetry11(finalPath, outputPath);
|
|
@@ -11507,8 +11577,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11507
11577
|
};
|
|
11508
11578
|
}
|
|
11509
11579
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11510
|
-
const planJsonPath =
|
|
11511
|
-
const chunksJsonPath =
|
|
11580
|
+
const planJsonPath = join29(planDir, "plan.json");
|
|
11581
|
+
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11512
11582
|
if (!existsSync21(planJsonPath)) {
|
|
11513
11583
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11514
11584
|
}
|
|
@@ -11559,7 +11629,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
11559
11629
|
return args;
|
|
11560
11630
|
}
|
|
11561
11631
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
11562
|
-
const cfrOutputPath =
|
|
11632
|
+
const cfrOutputPath = join29(ctx.workDir, `cfr.${ctx.format}`);
|
|
11563
11633
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
11564
11634
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
11565
11635
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -11586,7 +11656,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11586
11656
|
);
|
|
11587
11657
|
return;
|
|
11588
11658
|
}
|
|
11589
|
-
const paddedAudioPath =
|
|
11659
|
+
const paddedAudioPath = join29(ctx.workDir, "audio-padded.m4a");
|
|
11590
11660
|
const padTrimResult = await ctx.timed(
|
|
11591
11661
|
"audioMs",
|
|
11592
11662
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -11626,7 +11696,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11626
11696
|
}
|
|
11627
11697
|
}
|
|
11628
11698
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
11629
|
-
const listPath =
|
|
11699
|
+
const listPath = join29(workDir, "concat-list.txt");
|
|
11630
11700
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
11631
11701
|
writeFileSync11(listPath, `${body}
|
|
11632
11702
|
`, "utf-8");
|
|
@@ -11664,11 +11734,11 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11664
11734
|
return lockedAudio(planDir).separateAudio === true;
|
|
11665
11735
|
}
|
|
11666
11736
|
function planFormat(planDir) {
|
|
11667
|
-
const plan = JSON.parse(readFileSync10(
|
|
11737
|
+
const plan = JSON.parse(readFileSync10(join29(planDir, "plan.json"), "utf-8"));
|
|
11668
11738
|
return plan.dimensions.format;
|
|
11669
11739
|
}
|
|
11670
11740
|
function lockedAudio(planDir) {
|
|
11671
|
-
const encoderJsonPath =
|
|
11741
|
+
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11672
11742
|
if (!existsSync21(encoderJsonPath)) return {};
|
|
11673
11743
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11674
11744
|
}
|
|
@@ -11678,7 +11748,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11678
11748
|
`[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.`
|
|
11679
11749
|
);
|
|
11680
11750
|
}
|
|
11681
|
-
const encoderJsonPath =
|
|
11751
|
+
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11682
11752
|
if (!existsSync21(encoderJsonPath)) {
|
|
11683
11753
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11684
11754
|
}
|
|
@@ -11705,8 +11775,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11705
11775
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
11706
11776
|
}
|
|
11707
11777
|
for (const frame of frames) {
|
|
11708
|
-
const dst =
|
|
11709
|
-
cpSync3(
|
|
11778
|
+
const dst = join29(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
11779
|
+
cpSync3(join29(chunkDir, frame), dst);
|
|
11710
11780
|
globalIdx += 1;
|
|
11711
11781
|
}
|
|
11712
11782
|
}
|
|
@@ -11716,13 +11786,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11716
11786
|
);
|
|
11717
11787
|
}
|
|
11718
11788
|
if (audioPath !== null && existsSync21(audioPath)) {
|
|
11719
|
-
const sidecar =
|
|
11789
|
+
const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11720
11790
|
cpSync3(audioPath, sidecar);
|
|
11721
11791
|
}
|
|
11722
11792
|
let fileSize = 0;
|
|
11723
11793
|
for (const name of readdirSync8(outputPath)) {
|
|
11724
11794
|
try {
|
|
11725
|
-
fileSize += statSync8(
|
|
11795
|
+
fileSize += statSync8(join29(outputPath, name)).size;
|
|
11726
11796
|
} catch {
|
|
11727
11797
|
}
|
|
11728
11798
|
}
|
|
@@ -11738,7 +11808,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11738
11808
|
import { randomBytes } from "node:crypto";
|
|
11739
11809
|
import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11740
11810
|
import { cpus } from "node:os";
|
|
11741
|
-
import { join as
|
|
11811
|
+
import { join as join30 } from "node:path";
|
|
11742
11812
|
import {
|
|
11743
11813
|
assertSwiftShader,
|
|
11744
11814
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -11746,7 +11816,7 @@ import {
|
|
|
11746
11816
|
classifyCaptureFailure as classifyCaptureFailure4,
|
|
11747
11817
|
closeCaptureSession as closeCaptureSession11,
|
|
11748
11818
|
createCaptureSession as createCaptureSession11,
|
|
11749
|
-
createFrameLookupTable as
|
|
11819
|
+
createFrameLookupTable as createFrameLookupTable6,
|
|
11750
11820
|
deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
|
|
11751
11821
|
getEncoderPreset as getEncoderPreset6,
|
|
11752
11822
|
initializeSession as initializeSession10,
|
|
@@ -11862,7 +11932,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
11862
11932
|
if (kind === "file") return sha256Hex(readFileSync11(outputPath));
|
|
11863
11933
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11864
11934
|
const lines = entries.map(
|
|
11865
|
-
(name) => `${name}\0${sha256Hex(readFileSync11(
|
|
11935
|
+
(name) => `${name}\0${sha256Hex(readFileSync11(join30(outputPath, name)))}`
|
|
11866
11936
|
);
|
|
11867
11937
|
return sha256Hex(lines.join("\0"));
|
|
11868
11938
|
}
|
|
@@ -11879,9 +11949,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
11879
11949
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
11880
11950
|
const start = Date.now();
|
|
11881
11951
|
const log = defaultLogger;
|
|
11882
|
-
const planJsonPath =
|
|
11883
|
-
const encoderJsonPath =
|
|
11884
|
-
const chunksJsonPath =
|
|
11952
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
11953
|
+
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
11954
|
+
const chunksJsonPath = join30(planDir, "meta", "chunks.json");
|
|
11885
11955
|
if (!existsSync22(planJsonPath)) {
|
|
11886
11956
|
throw new RenderChunkValidationError(
|
|
11887
11957
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -11907,7 +11977,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11907
11977
|
}
|
|
11908
11978
|
const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
11909
11979
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11910
|
-
const videosJsonPath =
|
|
11980
|
+
const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11911
11981
|
let planVideos = null;
|
|
11912
11982
|
if (existsSync22(videosJsonPath)) {
|
|
11913
11983
|
try {
|
|
@@ -11939,7 +12009,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11939
12009
|
}
|
|
11940
12010
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11941
12011
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11942
|
-
const compiledDir =
|
|
12012
|
+
const compiledDir = join30(planDir, "compiled");
|
|
11943
12013
|
if (!existsSync22(compiledDir)) {
|
|
11944
12014
|
throw new RenderChunkValidationError(
|
|
11945
12015
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -11997,13 +12067,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11997
12067
|
};
|
|
11998
12068
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
11999
12069
|
mkdirSync14(workDir, { recursive: true });
|
|
12000
|
-
const framesDir =
|
|
12070
|
+
const framesDir = join30(workDir, "captured-frames");
|
|
12001
12071
|
mkdirSync14(framesDir, { recursive: true });
|
|
12002
12072
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
12003
12073
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
12004
12074
|
const chunkFps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
12005
12075
|
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
12006
|
-
const shownFrames = planVideos ?
|
|
12076
|
+
const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
12007
12077
|
const remoteVideoIds = new Set(
|
|
12008
12078
|
planVideos?.extracted.filter(
|
|
12009
12079
|
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
@@ -12022,7 +12092,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12022
12092
|
dimensions: plan.dimensions,
|
|
12023
12093
|
encoder,
|
|
12024
12094
|
videoCount: planVideos?.videos.length ?? 0,
|
|
12025
|
-
html: readFileSync11(
|
|
12095
|
+
html: readFileSync11(join30(compiledDir, "index.html"), "utf-8"),
|
|
12026
12096
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
12027
12097
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
12028
12098
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -12157,7 +12227,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12157
12227
|
deferredVideoFrames.catch(() => {
|
|
12158
12228
|
});
|
|
12159
12229
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
|
|
12160
|
-
(deferred) =>
|
|
12230
|
+
(deferred) => createFrameLookupTable6(
|
|
12161
12231
|
planVideos.videos,
|
|
12162
12232
|
[
|
|
12163
12233
|
...rebuildExtractedFramesFromPlanDir(
|
|
@@ -12192,7 +12262,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12192
12262
|
if (isPngSequence) {
|
|
12193
12263
|
if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12194
12264
|
} else {
|
|
12195
|
-
const outDir =
|
|
12265
|
+
const outDir = join30(outputChunkPath, "..");
|
|
12196
12266
|
if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12197
12267
|
}
|
|
12198
12268
|
const encodeInput = {
|
|
@@ -12436,8 +12506,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12436
12506
|
|
|
12437
12507
|
// src/services/distributed/planV2Execution.ts
|
|
12438
12508
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
12439
|
-
const workRoot = mkdtempSync6(
|
|
12440
|
-
const materializedPlanDir =
|
|
12509
|
+
const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-chunk-"));
|
|
12510
|
+
const materializedPlanDir = join31(workRoot, "plan");
|
|
12441
12511
|
try {
|
|
12442
12512
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
12443
12513
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
@@ -12446,8 +12516,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
|
12446
12516
|
}
|
|
12447
12517
|
}
|
|
12448
12518
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
12449
|
-
const workRoot = mkdtempSync6(
|
|
12450
|
-
const materializedPlanDir =
|
|
12519
|
+
const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-assembler-"));
|
|
12520
|
+
const materializedPlanDir = join31(workRoot, "plan");
|
|
12451
12521
|
try {
|
|
12452
12522
|
const materialized = materializePlanV2Target(
|
|
12453
12523
|
planV2Dir,
|
|
@@ -12739,7 +12809,7 @@ function validateIntDimension(field, value) {
|
|
|
12739
12809
|
// src/services/distributed/projectHash.ts
|
|
12740
12810
|
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
|
|
12741
12811
|
import { createHash as createHash8 } from "node:crypto";
|
|
12742
|
-
import { join as
|
|
12812
|
+
import { join as join32 } from "node:path";
|
|
12743
12813
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
12744
12814
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
12745
12815
|
function listSiteFiles(projectDir) {
|
|
@@ -12748,7 +12818,7 @@ function listSiteFiles(projectDir) {
|
|
|
12748
12818
|
function walk(dir, rel) {
|
|
12749
12819
|
for (const name of readdirSync10(dir).sort()) {
|
|
12750
12820
|
if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
|
|
12751
|
-
const full =
|
|
12821
|
+
const full = join32(dir, name);
|
|
12752
12822
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
12753
12823
|
let stat;
|
|
12754
12824
|
try {
|
|
@@ -12778,7 +12848,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
|
12778
12848
|
for (const rel of files) {
|
|
12779
12849
|
hash.update(rel);
|
|
12780
12850
|
hash.update("\0");
|
|
12781
|
-
const fd = openSync5(
|
|
12851
|
+
const fd = openSync5(join32(projectDir, rel), "r");
|
|
12782
12852
|
try {
|
|
12783
12853
|
let read;
|
|
12784
12854
|
while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
|