@hyperframes/producer 0.9.15 → 0.9.17
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 +489 -396
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +68 -68
- package/dist/index.js +1045 -671
- package/dist/public-server.js +739 -329
- 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/stages/compileStage.d.ts +12 -3
- package/dist/services/render/videoFrameWindow.d.ts +12 -0
- package/dist/services/renderOrchestrator.d.ts +2 -5
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,13 +8,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
8
8
|
import {
|
|
9
9
|
copyFileSync as copyFileSync6,
|
|
10
10
|
cpSync as cpSync2,
|
|
11
|
-
existsSync as
|
|
11
|
+
existsSync as existsSync18,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
15
|
-
writeFileSync as
|
|
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
|
|
@@ -71,12 +71,12 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
71
71
|
// src/services/renderOrchestrator.ts
|
|
72
72
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
73
73
|
import {
|
|
74
|
-
existsSync as
|
|
74
|
+
existsSync as existsSync13,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
78
78
|
statSync as statSync5,
|
|
79
|
-
writeFileSync as
|
|
79
|
+
writeFileSync as writeFileSync5,
|
|
80
80
|
copyFileSync as copyFileSync5,
|
|
81
81
|
appendFileSync
|
|
82
82
|
} from "fs";
|
|
@@ -90,16 +90,14 @@ import {
|
|
|
90
90
|
toFps as toFps2
|
|
91
91
|
} from "@hyperframes/core";
|
|
92
92
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
93
|
-
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
94
93
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
95
|
-
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
96
94
|
import {
|
|
97
95
|
extractMediaMetadata as extractMediaMetadata4,
|
|
98
96
|
resolveConfig as resolveConfig2,
|
|
99
97
|
closeCaptureSession as closeCaptureSession8,
|
|
100
98
|
createCaptureSession as createCaptureSession8,
|
|
101
99
|
initializeSession as initializeSession8,
|
|
102
|
-
createVideoFrameInjector,
|
|
100
|
+
createVideoFrameInjector as createVideoFrameInjector2,
|
|
103
101
|
getEncoderPreset as getEncoderPreset3,
|
|
104
102
|
distributeFrames as distributeFrames2,
|
|
105
103
|
distributeFramesInterleaved as distributeFramesInterleaved2,
|
|
@@ -123,13 +121,14 @@ import {
|
|
|
123
121
|
isDrawElementCaptureError,
|
|
124
122
|
getDrawElementVerificationDetails,
|
|
125
123
|
augmentProtocolTimeoutError,
|
|
126
|
-
augmentPageNavigationTimeoutError
|
|
124
|
+
augmentPageNavigationTimeoutError,
|
|
125
|
+
displaySize
|
|
127
126
|
} from "@hyperframes/engine";
|
|
128
127
|
import {
|
|
129
128
|
referenceBackgroundResult,
|
|
130
129
|
createReferenceBackgroundRecorder
|
|
131
130
|
} from "@hyperframes/engine";
|
|
132
|
-
import { join as
|
|
131
|
+
import { join as join16, dirname as dirname10, resolve as resolve7 } from "path";
|
|
133
132
|
import { randomUUID } from "crypto";
|
|
134
133
|
|
|
135
134
|
// src/services/fileServer.ts
|
|
@@ -1319,6 +1318,7 @@ import {
|
|
|
1319
1318
|
getCapturePerfSummary as getCapturePerfSummary2,
|
|
1320
1319
|
initializeSession as initializeSession2,
|
|
1321
1320
|
prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
|
|
1321
|
+
resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
|
|
1322
1322
|
shouldDisableBrowserPoolForParallelWorker,
|
|
1323
1323
|
spawnStreamingEncoder as spawnStreamingEncoder2
|
|
1324
1324
|
} from "@hyperframes/engine";
|
|
@@ -1547,6 +1547,85 @@ async function measureCaptureCostFromSession(session, totalFrames, fps, log) {
|
|
|
1547
1547
|
};
|
|
1548
1548
|
}
|
|
1549
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
|
+
|
|
1550
1629
|
// src/services/render/segmentManifest.ts
|
|
1551
1630
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
1552
1631
|
|
|
@@ -1914,14 +1993,17 @@ import {
|
|
|
1914
1993
|
} from "@hyperframes/engine";
|
|
1915
1994
|
|
|
1916
1995
|
// src/services/render/stages/compileStage.ts
|
|
1917
|
-
import {
|
|
1996
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1997
|
+
import { join as join8 } from "node:path";
|
|
1918
1998
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1999
|
+
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
2000
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
1919
2001
|
|
|
1920
2002
|
// src/services/htmlCompiler.ts
|
|
1921
2003
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1922
2004
|
import { createHash as createHash4 } from "node:crypto";
|
|
1923
2005
|
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1924
|
-
import { join as
|
|
2006
|
+
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1925
2007
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1926
2008
|
import {
|
|
1927
2009
|
COMPOSITION_SOURCE_URL,
|
|
@@ -2168,7 +2250,7 @@ import {
|
|
|
2168
2250
|
// src/services/animatedGifPrep.ts
|
|
2169
2251
|
import { createHash as createHash2 } from "node:crypto";
|
|
2170
2252
|
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
2171
|
-
import { dirname as
|
|
2253
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
|
|
2172
2254
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
2173
2255
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
2174
2256
|
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
@@ -2324,11 +2406,11 @@ async function runAnimatedGifTranscode(request) {
|
|
|
2324
2406
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
2325
2407
|
}
|
|
2326
2408
|
async function ensurePreparedWebm(input) {
|
|
2327
|
-
if (!existsSync4(
|
|
2328
|
-
mkdirSync2(
|
|
2409
|
+
if (!existsSync4(dirname4(input.cachePath))) {
|
|
2410
|
+
mkdirSync2(dirname4(input.cachePath), { recursive: true });
|
|
2329
2411
|
}
|
|
2330
|
-
if (!existsSync4(
|
|
2331
|
-
mkdirSync2(
|
|
2412
|
+
if (!existsSync4(dirname4(input.outputPath))) {
|
|
2413
|
+
mkdirSync2(dirname4(input.outputPath), { recursive: true });
|
|
2332
2414
|
}
|
|
2333
2415
|
if (!isUsableFile(input.cachePath)) {
|
|
2334
2416
|
const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -2411,7 +2493,7 @@ function replaceImageWithVideo(input) {
|
|
|
2411
2493
|
return video;
|
|
2412
2494
|
}
|
|
2413
2495
|
async function prepareAnimatedGifInputs(html, options) {
|
|
2414
|
-
const outputDir = options.outputDir ??
|
|
2496
|
+
const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_GIF_SUBDIR);
|
|
2415
2497
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
|
|
2416
2498
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
2417
2499
|
const { document: document2 } = parseHTML3(html);
|
|
@@ -2438,8 +2520,8 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2438
2520
|
);
|
|
2439
2521
|
const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
|
|
2440
2522
|
const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
|
|
2441
|
-
const cachePath =
|
|
2442
|
-
const outputPath =
|
|
2523
|
+
const cachePath = join5(cacheDir, filename);
|
|
2524
|
+
const outputPath = join5(outputDir, filename);
|
|
2443
2525
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2444
2526
|
await ensurePreparedWebm({
|
|
2445
2527
|
sourcePath,
|
|
@@ -2484,7 +2566,7 @@ import {
|
|
|
2484
2566
|
statSync as statSync3,
|
|
2485
2567
|
writeFileSync as writeFileSync2
|
|
2486
2568
|
} from "node:fs";
|
|
2487
|
-
import { dirname as
|
|
2569
|
+
import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
|
|
2488
2570
|
import { parseHTML as parseHTML4 } from "linkedom";
|
|
2489
2571
|
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
2490
2572
|
|
|
@@ -2730,8 +2812,8 @@ function isUsableFile2(path) {
|
|
|
2730
2812
|
}
|
|
2731
2813
|
}
|
|
2732
2814
|
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
2733
|
-
mkdirSync3(
|
|
2734
|
-
mkdirSync3(
|
|
2815
|
+
mkdirSync3(dirname5(cachePath), { recursive: true });
|
|
2816
|
+
mkdirSync3(dirname5(outputPath), { recursive: true });
|
|
2735
2817
|
if (!isUsableFile2(cachePath)) {
|
|
2736
2818
|
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
2737
2819
|
const inlinePath = `${tmpPath}.heic`;
|
|
@@ -2758,7 +2840,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
|
2758
2840
|
}
|
|
2759
2841
|
}
|
|
2760
2842
|
async function prepareHeifImageInputs(html, options) {
|
|
2761
|
-
const outputDir = options.outputDir ??
|
|
2843
|
+
const outputDir = options.outputDir ?? join6(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
2762
2844
|
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
2763
2845
|
const cacheDir = options.cacheDir ?? outputDir;
|
|
2764
2846
|
const preparedAssets = /* @__PURE__ */ new Map();
|
|
@@ -2779,7 +2861,7 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
2779
2861
|
await Promise.all(
|
|
2780
2862
|
[...unique].map(
|
|
2781
2863
|
([filename, source]) => withMediaProbeSlot(
|
|
2782
|
-
() => ensurePreparedJpeg(source,
|
|
2864
|
+
() => ensurePreparedJpeg(source, join6(cacheDir, filename), join6(outputDir, filename), options)
|
|
2783
2865
|
).catch((error) => {
|
|
2784
2866
|
if (!options.onFailure) throw error;
|
|
2785
2867
|
failed.add(filename);
|
|
@@ -2791,8 +2873,8 @@ async function prepareHeifImageInputs(html, options) {
|
|
|
2791
2873
|
if (failed.has(filename)) continue;
|
|
2792
2874
|
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2793
2875
|
img.setAttribute("src", outputSrc);
|
|
2794
|
-
preparedAssets.set(outputSrc,
|
|
2795
|
-
preparedImages.push({ sourceSrc: src, outputSrc, outputPath:
|
|
2876
|
+
preparedAssets.set(outputSrc, join6(outputDir, filename));
|
|
2877
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join6(outputDir, filename) });
|
|
2796
2878
|
}
|
|
2797
2879
|
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
2798
2880
|
}
|
|
@@ -3116,7 +3198,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
3116
3198
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
|
|
3117
3199
|
let filePath = src;
|
|
3118
3200
|
if (isHttpUrl(src)) {
|
|
3119
|
-
const remoteDir =
|
|
3201
|
+
const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
3120
3202
|
if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3121
3203
|
try {
|
|
3122
3204
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
@@ -3127,7 +3209,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3127
3209
|
}
|
|
3128
3210
|
} else {
|
|
3129
3211
|
filePath = decodedUrlPath(src);
|
|
3130
|
-
if (!filePath.startsWith("/")) filePath =
|
|
3212
|
+
if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
|
|
3131
3213
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
3132
3214
|
}
|
|
3133
3215
|
if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
|
|
@@ -3288,7 +3370,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3288
3370
|
workItems.map(async (item) => {
|
|
3289
3371
|
const { html: compiledSub } = await compileHtmlFile(
|
|
3290
3372
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
3291
|
-
|
|
3373
|
+
dirname6(item.filePath),
|
|
3292
3374
|
downloadDir,
|
|
3293
3375
|
void 0,
|
|
3294
3376
|
urlByAbsentLocalPath,
|
|
@@ -3765,7 +3847,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
3765
3847
|
return downloadAndRewriteUrls(
|
|
3766
3848
|
urlSet,
|
|
3767
3849
|
html,
|
|
3768
|
-
|
|
3850
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3769
3851
|
"Remote media download failed",
|
|
3770
3852
|
"Localized remote media source(s)"
|
|
3771
3853
|
);
|
|
@@ -3780,7 +3862,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
3780
3862
|
return downloadAndRewriteUrls(
|
|
3781
3863
|
urlSet,
|
|
3782
3864
|
html,
|
|
3783
|
-
|
|
3865
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3784
3866
|
"Remote image download failed",
|
|
3785
3867
|
"Localized remote image source(s)"
|
|
3786
3868
|
);
|
|
@@ -3796,7 +3878,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
3796
3878
|
return downloadAndRewriteUrls(
|
|
3797
3879
|
urlSet,
|
|
3798
3880
|
html,
|
|
3799
|
-
|
|
3881
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3800
3882
|
"Remote background-image download failed",
|
|
3801
3883
|
"Localized remote background-image(s)",
|
|
3802
3884
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -3906,7 +3988,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
3906
3988
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3907
3989
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3908
3990
|
const path = decodedUrlPath(url);
|
|
3909
|
-
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(
|
|
3991
|
+
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
|
|
3910
3992
|
return isPathInside2(file, projectDir) && existsSync6(file);
|
|
3911
3993
|
});
|
|
3912
3994
|
if (family && shipsFile) families.push(family);
|
|
@@ -3948,7 +4030,7 @@ function localizeFontFaceUrls(html, downloadDir) {
|
|
|
3948
4030
|
return downloadAndRewriteUrls(
|
|
3949
4031
|
collectFontFaceUrls(html),
|
|
3950
4032
|
html,
|
|
3951
|
-
|
|
4033
|
+
join7(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3952
4034
|
"Remote font download failed",
|
|
3953
4035
|
"Localized remote font face(s)",
|
|
3954
4036
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -4620,11 +4702,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
4620
4702
|
});
|
|
4621
4703
|
return flipped;
|
|
4622
4704
|
}
|
|
4705
|
+
function resolveEntryHtmlPath(input) {
|
|
4706
|
+
const { projectDir, workDir, entryFile, log } = input;
|
|
4707
|
+
const htmlPath = join8(projectDir, entryFile);
|
|
4708
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
4709
|
+
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4710
|
+
const projectIndexPath = join8(projectDir, "index.html");
|
|
4711
|
+
if (!existsSync7(projectIndexPath)) {
|
|
4712
|
+
throw new Error(
|
|
4713
|
+
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
4717
|
+
readTextFile2(projectIndexPath),
|
|
4718
|
+
entryFile,
|
|
4719
|
+
rawEntry
|
|
4720
|
+
);
|
|
4721
|
+
if (!standaloneHtml) {
|
|
4722
|
+
throw new Error(
|
|
4723
|
+
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
4724
|
+
);
|
|
4725
|
+
}
|
|
4726
|
+
const wrapperPath = join8(workDir, "standalone-entry.html");
|
|
4727
|
+
writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
|
|
4728
|
+
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
4729
|
+
return wrapperPath;
|
|
4730
|
+
}
|
|
4623
4731
|
async function runCompileStage(input) {
|
|
4624
4732
|
const {
|
|
4625
4733
|
projectDir,
|
|
4626
4734
|
workDir,
|
|
4627
|
-
htmlPath,
|
|
4628
4735
|
entryFile,
|
|
4629
4736
|
job,
|
|
4630
4737
|
cfg,
|
|
@@ -4635,8 +4742,9 @@ async function runCompileStage(input) {
|
|
|
4635
4742
|
allowSystemFontCapture,
|
|
4636
4743
|
abortSignal
|
|
4637
4744
|
} = input;
|
|
4745
|
+
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
4638
4746
|
const compileStart = Date.now();
|
|
4639
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
4747
|
+
const compiled = await compileForRender(projectDir, htmlPath, join8(workDir, "downloads"), {
|
|
4640
4748
|
log,
|
|
4641
4749
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
4642
4750
|
allowSystemFontCapture,
|
|
@@ -4644,7 +4752,7 @@ async function runCompileStage(input) {
|
|
|
4644
4752
|
variables: input.variables,
|
|
4645
4753
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
4646
4754
|
pageMotionBlur: input.pageMotionBlur,
|
|
4647
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
4755
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join8(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
4648
4756
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
4649
4757
|
});
|
|
4650
4758
|
assertNotAborted();
|
|
@@ -4701,7 +4809,7 @@ async function runCompileStage(input) {
|
|
|
4701
4809
|
};
|
|
4702
4810
|
await preflightCompositionAssetMediaTypes({
|
|
4703
4811
|
projectDir,
|
|
4704
|
-
compiledDir:
|
|
4812
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4705
4813
|
composition,
|
|
4706
4814
|
signal: abortSignal
|
|
4707
4815
|
});
|
|
@@ -4747,7 +4855,7 @@ async function runCompileStage(input) {
|
|
|
4747
4855
|
|
|
4748
4856
|
// src/services/render/stages/probeStage.ts
|
|
4749
4857
|
import { createHash as createHash5 } from "node:crypto";
|
|
4750
|
-
import { basename as basename4, join as
|
|
4858
|
+
import { basename as basename4, join as join9 } from "node:path";
|
|
4751
4859
|
import { parseHTML as parseHTML6 } from "linkedom";
|
|
4752
4860
|
import {
|
|
4753
4861
|
closeCaptureSession as closeCaptureSession4,
|
|
@@ -4956,7 +5064,7 @@ async function runProbeStage(input) {
|
|
|
4956
5064
|
});
|
|
4957
5065
|
fileServer = await createFileServer({
|
|
4958
5066
|
projectDir,
|
|
4959
|
-
compiledDir:
|
|
5067
|
+
compiledDir: join9(workDir, "compiled"),
|
|
4960
5068
|
port: 0,
|
|
4961
5069
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
4962
5070
|
fps: job.config.fps
|
|
@@ -4986,7 +5094,7 @@ async function runProbeStage(input) {
|
|
|
4986
5094
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
4987
5095
|
probeSession = await createCaptureSession4(
|
|
4988
5096
|
fileServer.url,
|
|
4989
|
-
|
|
5097
|
+
join9(workDir, "probe"),
|
|
4990
5098
|
captureOpts,
|
|
4991
5099
|
null,
|
|
4992
5100
|
probeCfg
|
|
@@ -5071,7 +5179,7 @@ async function runProbeStage(input) {
|
|
|
5071
5179
|
});
|
|
5072
5180
|
probeSession = await createCaptureSession4(
|
|
5073
5181
|
fileServer.url,
|
|
5074
|
-
|
|
5182
|
+
join9(workDir, "probe-screenshot"),
|
|
5075
5183
|
captureOpts,
|
|
5076
5184
|
null,
|
|
5077
5185
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -5109,7 +5217,7 @@ async function runProbeStage(input) {
|
|
|
5109
5217
|
compiled,
|
|
5110
5218
|
resolutions,
|
|
5111
5219
|
projectDir,
|
|
5112
|
-
|
|
5220
|
+
join9(workDir, "downloads")
|
|
5113
5221
|
);
|
|
5114
5222
|
assertNotAborted();
|
|
5115
5223
|
composition.videos = compiled.videos;
|
|
@@ -5300,7 +5408,7 @@ async function runProbeStage(input) {
|
|
|
5300
5408
|
}
|
|
5301
5409
|
await preflightCompositionAssetMediaTypes({
|
|
5302
5410
|
projectDir,
|
|
5303
|
-
compiledDir:
|
|
5411
|
+
compiledDir: join9(workDir, "compiled"),
|
|
5304
5412
|
composition,
|
|
5305
5413
|
signal: abortSignal
|
|
5306
5414
|
});
|
|
@@ -5466,11 +5574,11 @@ function validateDistributedDuration(input) {
|
|
|
5466
5574
|
}
|
|
5467
5575
|
|
|
5468
5576
|
// src/services/render/stages/extractVideosStage.ts
|
|
5469
|
-
import { existsSync as
|
|
5470
|
-
import { join as
|
|
5577
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5578
|
+
import { join as join10 } from "node:path";
|
|
5471
5579
|
import {
|
|
5472
5580
|
classifyVideoExtractionError,
|
|
5473
|
-
createFrameLookupTable,
|
|
5581
|
+
createFrameLookupTable as createFrameLookupTable3,
|
|
5474
5582
|
detectTransfer,
|
|
5475
5583
|
extractAllVideoFrames,
|
|
5476
5584
|
extractMediaMetadata as extractMediaMetadata3,
|
|
@@ -5482,7 +5590,7 @@ import {
|
|
|
5482
5590
|
resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
|
|
5483
5591
|
isHttpUrl as isHttpUrl2,
|
|
5484
5592
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
5485
|
-
runVideoExtractionWithRetry,
|
|
5593
|
+
runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
|
|
5486
5594
|
safeVideoExtractionSourceIdentity,
|
|
5487
5595
|
videoAudioId as videoAudioId2
|
|
5488
5596
|
} from "@hyperframes/engine";
|
|
@@ -5741,9 +5849,9 @@ async function runExtractVideosStage(input) {
|
|
|
5741
5849
|
const probeFailures = await Promise.all(
|
|
5742
5850
|
composition.videos.map(async (v) => {
|
|
5743
5851
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5744
|
-
if (!
|
|
5852
|
+
if (!existsSync8(videoPath)) return null;
|
|
5745
5853
|
try {
|
|
5746
|
-
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), {
|
|
5747
5855
|
signal: abortSignal,
|
|
5748
5856
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5749
5857
|
onRetry: () => {
|
|
@@ -5779,7 +5887,7 @@ async function runExtractVideosStage(input) {
|
|
|
5779
5887
|
const probed = await Promise.all(
|
|
5780
5888
|
composition.images.map(async (img) => {
|
|
5781
5889
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
5782
|
-
if (!
|
|
5890
|
+
if (!existsSync8(imgPath)) return null;
|
|
5783
5891
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
5784
5892
|
if (isHdrColorSpace2(colorSpace)) {
|
|
5785
5893
|
nativeHdrImageIds.add(img.id);
|
|
@@ -5813,14 +5921,14 @@ async function runExtractVideosStage(input) {
|
|
|
5813
5921
|
// because short boundary counts can differ by one frame.
|
|
5814
5922
|
{
|
|
5815
5923
|
fps: job.config.fps,
|
|
5816
|
-
outputDir:
|
|
5924
|
+
outputDir: join10(compiledDir, "__hyperframes_video_frames"),
|
|
5817
5925
|
format: job.config.videoFrameFormat ?? "auto",
|
|
5818
5926
|
toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
|
|
5819
5927
|
timelineEnd: composition.duration,
|
|
5820
5928
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5821
5929
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
5822
5930
|
deferRangeExtraction,
|
|
5823
|
-
contentKeyedDirs: [
|
|
5931
|
+
contentKeyedDirs: [join10(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
5824
5932
|
urlByAbsentLocalPath,
|
|
5825
5933
|
onProgress: ({ planned = 0, done = 0 }) => {
|
|
5826
5934
|
framesPlanned += planned;
|
|
@@ -5840,7 +5948,7 @@ async function runExtractVideosStage(input) {
|
|
|
5840
5948
|
materializeSymlinks
|
|
5841
5949
|
});
|
|
5842
5950
|
if (extractionResult.extracted.length > 0) {
|
|
5843
|
-
frameLookup =
|
|
5951
|
+
frameLookup = createFrameLookupTable3(
|
|
5844
5952
|
composition.videos,
|
|
5845
5953
|
extractionResult.extracted,
|
|
5846
5954
|
resolveRenderFpsConfig(job.config.fps).value
|
|
@@ -5899,7 +6007,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
5899
6007
|
videos.map(async (video) => {
|
|
5900
6008
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
5901
6009
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
5902
|
-
if (!isHttpUrl2(readFrom) && !
|
|
6010
|
+
if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
|
|
5903
6011
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
5904
6012
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
5905
6013
|
readFrom,
|
|
@@ -5927,8 +6035,8 @@ import {
|
|
|
5927
6035
|
} from "@hyperframes/engine";
|
|
5928
6036
|
|
|
5929
6037
|
// src/services/render/stages/audioStage.ts
|
|
5930
|
-
import { existsSync as
|
|
5931
|
-
import { join as
|
|
6038
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6039
|
+
import { join as join13 } from "node:path";
|
|
5932
6040
|
import {
|
|
5933
6041
|
MIXED_AUDIO_FILENAME,
|
|
5934
6042
|
audioFailureDiagnostics,
|
|
@@ -5938,8 +6046,8 @@ import {
|
|
|
5938
6046
|
} from "@hyperframes/engine";
|
|
5939
6047
|
|
|
5940
6048
|
// src/services/render/audioDeliveryLimit.ts
|
|
5941
|
-
import { existsSync as
|
|
5942
|
-
import { dirname as
|
|
6049
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
6050
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
5943
6051
|
import {
|
|
5944
6052
|
applyAudioFxChain,
|
|
5945
6053
|
describeFfmpegFailure as describeFfmpegFailure3,
|
|
@@ -5950,7 +6058,7 @@ import {
|
|
|
5950
6058
|
// src/services/render/audioPadTrim.ts
|
|
5951
6059
|
import { spawn } from "node:child_process";
|
|
5952
6060
|
import { mkdtempSync } from "node:fs";
|
|
5953
|
-
import { dirname as
|
|
6061
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5954
6062
|
import {
|
|
5955
6063
|
DEFAULT_CONFIG,
|
|
5956
6064
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -6229,7 +6337,7 @@ async function constrainAacTruePeak(input) {
|
|
|
6229
6337
|
}
|
|
6230
6338
|
async function cutSource(input, scratchDir, attenuationDb) {
|
|
6231
6339
|
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
6232
|
-
const cutPath =
|
|
6340
|
+
const cutPath = join11(scratchDir, "source.wav");
|
|
6233
6341
|
try {
|
|
6234
6342
|
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
6235
6343
|
if (!result.success) throw new Error(result.error);
|
|
@@ -6243,8 +6351,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
|
|
|
6243
6351
|
async function enforceAacTruePeak(input) {
|
|
6244
6352
|
let scratchDir;
|
|
6245
6353
|
try {
|
|
6246
|
-
scratchDir = mkdtempSync(
|
|
6247
|
-
const correctedPath =
|
|
6354
|
+
scratchDir = mkdtempSync(join11(dirname7(input.audioPath), ".true-peak-"));
|
|
6355
|
+
const correctedPath = join11(scratchDir, "audio.m4a");
|
|
6248
6356
|
const measurements = [];
|
|
6249
6357
|
let attenuationDb = 0;
|
|
6250
6358
|
let sourcePeakDbfs;
|
|
@@ -6435,31 +6543,57 @@ async function runFfprobeJson(args, signal) {
|
|
|
6435
6543
|
}
|
|
6436
6544
|
|
|
6437
6545
|
// src/services/render/audioDeliveryLimit.ts
|
|
6438
|
-
var
|
|
6439
|
-
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) => ({
|
|
6440
6550
|
version: 1,
|
|
6441
|
-
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling
|
|
6442
|
-
};
|
|
6551
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
|
|
6552
|
+
});
|
|
6443
6553
|
async function limitDeliveryPeaks(input, deps = {
|
|
6444
6554
|
measure: measureTruePeakDbfs,
|
|
6445
6555
|
applyChain: applyAudioFxChain,
|
|
6446
6556
|
encode: encodeAac2,
|
|
6447
6557
|
rename: renameWithRetry6,
|
|
6448
|
-
sourceExists:
|
|
6558
|
+
sourceExists: existsSync9
|
|
6449
6559
|
}) {
|
|
6450
6560
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6451
6561
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
6452
6562
|
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
6453
6563
|
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
6454
|
-
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;
|
|
6455
6586
|
try {
|
|
6456
|
-
await deps.applyChain(sourcePath,
|
|
6587
|
+
await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
|
|
6588
|
+
trackId: "delivery",
|
|
6589
|
+
signal
|
|
6590
|
+
});
|
|
6457
6591
|
} catch (error) {
|
|
6458
6592
|
if (signal?.aborted) throw error;
|
|
6459
6593
|
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
6460
6594
|
error: error instanceof Error ? error.message : String(error)
|
|
6461
6595
|
});
|
|
6462
|
-
return {
|
|
6596
|
+
return { limitFailed: true };
|
|
6463
6597
|
}
|
|
6464
6598
|
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
6465
6599
|
signal,
|
|
@@ -6467,13 +6601,14 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6467
6601
|
});
|
|
6468
6602
|
if (!encoded.success) {
|
|
6469
6603
|
return {
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6604
|
+
encodeFailure: {
|
|
6605
|
+
success: false,
|
|
6606
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
6607
|
+
failureReason: encoded.failureReason
|
|
6608
|
+
}
|
|
6473
6609
|
};
|
|
6474
6610
|
}
|
|
6475
|
-
deps.
|
|
6476
|
-
return { success: true };
|
|
6611
|
+
return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
|
|
6477
6612
|
}
|
|
6478
6613
|
|
|
6479
6614
|
// src/services/render/stages/audioStage.ts
|
|
@@ -6499,7 +6634,7 @@ function runAudioStage(input) {
|
|
|
6499
6634
|
}
|
|
6500
6635
|
async function runAudioStageScoped(input) {
|
|
6501
6636
|
const stageStart = Date.now();
|
|
6502
|
-
const pcmSourcePath =
|
|
6637
|
+
const pcmSourcePath = join13(input.workDir, "audio-source.wav");
|
|
6503
6638
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6504
6639
|
let deliverySourcePath;
|
|
6505
6640
|
try {
|
|
@@ -6515,7 +6650,7 @@ async function runAudioStageScoped(input) {
|
|
|
6515
6650
|
);
|
|
6516
6651
|
audioLoweredDb = guard.loweredDb;
|
|
6517
6652
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6518
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
6653
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
|
|
6519
6654
|
deliverySourcePath = pcmSourcePath;
|
|
6520
6655
|
}
|
|
6521
6656
|
}
|
|
@@ -6564,7 +6699,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6564
6699
|
assertNotAborted,
|
|
6565
6700
|
log
|
|
6566
6701
|
} = input;
|
|
6567
|
-
const audioOutputPath =
|
|
6702
|
+
const audioOutputPath = join13(workDir, MIXED_AUDIO_FILENAME);
|
|
6568
6703
|
let hasAudio = false;
|
|
6569
6704
|
let audioError;
|
|
6570
6705
|
let audioFailures;
|
|
@@ -6575,7 +6710,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6575
6710
|
audioResult = await processCompositionAudio(
|
|
6576
6711
|
audios,
|
|
6577
6712
|
projectDir,
|
|
6578
|
-
|
|
6713
|
+
join13(workDir, "audio-work"),
|
|
6579
6714
|
audioOutputPath,
|
|
6580
6715
|
duration,
|
|
6581
6716
|
abortSignal,
|
|
@@ -6643,8 +6778,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6643
6778
|
}
|
|
6644
6779
|
|
|
6645
6780
|
// src/services/render/stages/captureStage.ts
|
|
6646
|
-
import { existsSync as
|
|
6647
|
-
import { join as
|
|
6781
|
+
import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
|
|
6782
|
+
import { join as join14 } from "node:path";
|
|
6648
6783
|
import {
|
|
6649
6784
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
6650
6785
|
frameFileExtension,
|
|
@@ -6695,16 +6830,16 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6695
6830
|
}
|
|
6696
6831
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6697
6832
|
let total = sampleDirectoryBytes(framesDir);
|
|
6698
|
-
if (!
|
|
6833
|
+
if (!existsSync11(workDir)) return total;
|
|
6699
6834
|
for (const attempt of readdirSync(workDir)) {
|
|
6700
6835
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6701
|
-
total += sampleDirectoryBytes(
|
|
6836
|
+
total += sampleDirectoryBytes(join14(workDir, attempt));
|
|
6702
6837
|
continue;
|
|
6703
6838
|
}
|
|
6704
6839
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
6705
|
-
const attemptDir =
|
|
6840
|
+
const attemptDir = join14(workDir, attempt);
|
|
6706
6841
|
for (const name of readdirSync(attemptDir)) {
|
|
6707
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
6842
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join14(attemptDir, name));
|
|
6708
6843
|
}
|
|
6709
6844
|
}
|
|
6710
6845
|
return total;
|
|
@@ -7032,16 +7167,16 @@ import {
|
|
|
7032
7167
|
// src/services/render/stages/encodeStage.ts
|
|
7033
7168
|
import {
|
|
7034
7169
|
copyFileSync as copyFileSync4,
|
|
7035
|
-
existsSync as
|
|
7170
|
+
existsSync as existsSync12,
|
|
7036
7171
|
mkdirSync as mkdirSync5,
|
|
7037
7172
|
readdirSync as readdirSync2,
|
|
7038
7173
|
readFileSync as readFileSync5,
|
|
7039
7174
|
rmSync as rmSync4,
|
|
7040
7175
|
statSync as statSync4,
|
|
7041
|
-
writeFileSync as
|
|
7176
|
+
writeFileSync as writeFileSync4
|
|
7042
7177
|
} from "node:fs";
|
|
7043
7178
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
7044
|
-
import { dirname as
|
|
7179
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7045
7180
|
import {
|
|
7046
7181
|
encodeFramesChunkedConcat,
|
|
7047
7182
|
encodeFramesFromDir,
|
|
@@ -7134,7 +7269,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
7134
7269
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
7135
7270
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
7136
7271
|
}
|
|
7137
|
-
|
|
7272
|
+
writeFileSync4(outputPath, gif);
|
|
7138
7273
|
return null;
|
|
7139
7274
|
}
|
|
7140
7275
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -7166,7 +7301,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7166
7301
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7167
7302
|
if (failure) return failure;
|
|
7168
7303
|
}
|
|
7169
|
-
const fileSize =
|
|
7304
|
+
const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
|
|
7170
7305
|
return {
|
|
7171
7306
|
success: true,
|
|
7172
7307
|
outputPath,
|
|
@@ -7252,7 +7387,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
7252
7387
|
await new Promise((resolve10) => wake = resolve10);
|
|
7253
7388
|
}
|
|
7254
7389
|
if (signal.aborted) return;
|
|
7255
|
-
const mergedPath =
|
|
7390
|
+
const mergedPath = join15(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
7256
7391
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
7257
7392
|
landed.delete(next);
|
|
7258
7393
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -7327,7 +7462,7 @@ async function runEncodeStage(input) {
|
|
|
7327
7462
|
const stage5Start = Date.now();
|
|
7328
7463
|
if (isPngSequence) {
|
|
7329
7464
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7330
|
-
if (!
|
|
7465
|
+
if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7331
7466
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7332
7467
|
if (captured.length === 0) {
|
|
7333
7468
|
throw new Error(
|
|
@@ -7335,11 +7470,11 @@ async function runEncodeStage(input) {
|
|
|
7335
7470
|
);
|
|
7336
7471
|
}
|
|
7337
7472
|
captured.forEach((name, i) => {
|
|
7338
|
-
const dst =
|
|
7339
|
-
copyFileSync4(
|
|
7473
|
+
const dst = join15(outputPath, formatExportFrameName(i, "png"));
|
|
7474
|
+
copyFileSync4(join15(framesDir, name), dst);
|
|
7340
7475
|
});
|
|
7341
|
-
if (hasAudio && audioOutputPath &&
|
|
7342
|
-
copyFileSync4(audioOutputPath,
|
|
7476
|
+
if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
|
|
7477
|
+
copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7343
7478
|
log.info(
|
|
7344
7479
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
7345
7480
|
);
|
|
@@ -7359,7 +7494,7 @@ async function runEncodeStage(input) {
|
|
|
7359
7494
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
7360
7495
|
fps: job.config.fps,
|
|
7361
7496
|
loop,
|
|
7362
|
-
palettePath:
|
|
7497
|
+
palettePath: join15(dirname9(videoOnlyPath), "gif-palette.png"),
|
|
7363
7498
|
preserveAlpha: needsAlpha,
|
|
7364
7499
|
signal: abortSignal,
|
|
7365
7500
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -7418,7 +7553,7 @@ function sampleDirectoryBytes(dir) {
|
|
|
7418
7553
|
continue;
|
|
7419
7554
|
}
|
|
7420
7555
|
for (const name of entries) {
|
|
7421
|
-
const full =
|
|
7556
|
+
const full = join16(current, name);
|
|
7422
7557
|
try {
|
|
7423
7558
|
const st = statSync5(full);
|
|
7424
7559
|
if (st.isDirectory()) {
|
|
@@ -7494,18 +7629,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
7494
7629
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
7495
7630
|
}
|
|
7496
7631
|
function collectVideoMetadataHints(extractedVideos) {
|
|
7497
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
7498
|
-
id: video.videoId,
|
|
7499
|
-
width: video.metadata.width,
|
|
7500
|
-
height: video.metadata.height
|
|
7501
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
7632
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
|
|
7502
7633
|
}
|
|
7503
7634
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
7504
7635
|
const ranges = [];
|
|
7505
7636
|
let rangeStart = null;
|
|
7506
7637
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7507
|
-
const framePath =
|
|
7508
|
-
const missing = !
|
|
7638
|
+
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7639
|
+
const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
|
|
7509
7640
|
if (missing && rangeStart === null) {
|
|
7510
7641
|
rangeStart = frameIndex;
|
|
7511
7642
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7521,7 +7652,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7521
7652
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
7522
7653
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
7523
7654
|
const batches = [];
|
|
7524
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
7655
|
+
const outputDirFor = (batchIndex, workerId) => join16(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
7525
7656
|
if (stride > 1) {
|
|
7526
7657
|
const spans = /* @__PURE__ */ new Map();
|
|
7527
7658
|
for (const range of ranges) {
|
|
@@ -7594,8 +7725,8 @@ function isRecoverableParallelCaptureError(error) {
|
|
|
7594
7725
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
7595
7726
|
let captured = 0;
|
|
7596
7727
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7597
|
-
const framePath =
|
|
7598
|
-
if (
|
|
7728
|
+
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7729
|
+
if (existsSync13(framePath)) captured++;
|
|
7599
7730
|
}
|
|
7600
7731
|
return captured;
|
|
7601
7732
|
}
|
|
@@ -7620,7 +7751,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
7620
7751
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
7621
7752
|
});
|
|
7622
7753
|
pendingTransientRetry = false;
|
|
7623
|
-
const attemptWorkDir =
|
|
7754
|
+
const attemptWorkDir = join16(options.workDir, `capture-attempt-${attempt}`);
|
|
7624
7755
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
7625
7756
|
missingRanges,
|
|
7626
7757
|
currentWorkers,
|
|
@@ -7813,28 +7944,26 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
|
|
|
7813
7944
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
7814
7945
|
|
|
7815
7946
|
// src/services/distributed/planFrameCost.ts
|
|
7816
|
-
import { join as
|
|
7947
|
+
import { join as join19 } from "node:path";
|
|
7817
7948
|
import {
|
|
7818
7949
|
closeCaptureSession as closeCaptureSession9,
|
|
7819
7950
|
createCaptureSession as createCaptureSession9,
|
|
7820
|
-
createFrameLookupTable as
|
|
7951
|
+
createFrameLookupTable as createFrameLookupTable4,
|
|
7821
7952
|
initializeSession as initializeSession9
|
|
7822
7953
|
} from "@hyperframes/engine";
|
|
7823
7954
|
|
|
7824
7955
|
// src/services/distributed/chunkFrames.ts
|
|
7825
|
-
import { existsSync as
|
|
7826
|
-
import { extname as extname2, join as
|
|
7956
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
|
|
7957
|
+
import { extname as extname2, join as join18 } from "node:path";
|
|
7827
7958
|
import {
|
|
7828
7959
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
7829
|
-
|
|
7830
|
-
createVideoFrameInjector as createVideoFrameInjector2,
|
|
7831
|
-
extractVideoFramesRange
|
|
7960
|
+
createVideoFrameInjector as createVideoFrameInjector3
|
|
7832
7961
|
} from "@hyperframes/engine";
|
|
7833
7962
|
|
|
7834
7963
|
// src/services/distributed/shared.ts
|
|
7835
7964
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7836
|
-
import { dirname as
|
|
7837
|
-
import { existsSync as
|
|
7965
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
7966
|
+
import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
|
|
7838
7967
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7839
7968
|
import { promisify as promisify2 } from "node:util";
|
|
7840
7969
|
import {
|
|
@@ -7849,8 +7978,8 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7849
7978
|
}
|
|
7850
7979
|
function resolvePlanAudioPath(planDir) {
|
|
7851
7980
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7852
|
-
const candidate =
|
|
7853
|
-
if (
|
|
7981
|
+
const candidate = join17(planDir, name);
|
|
7982
|
+
if (existsSync14(candidate)) return candidate;
|
|
7854
7983
|
}
|
|
7855
7984
|
return null;
|
|
7856
7985
|
}
|
|
@@ -8107,11 +8236,11 @@ function buildSyntheticRenderJob(input) {
|
|
|
8107
8236
|
var cachedProducerVersion = null;
|
|
8108
8237
|
function readProducerVersion() {
|
|
8109
8238
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
8110
|
-
const startDir =
|
|
8239
|
+
const startDir = dirname11(fileURLToPath3(import.meta.url));
|
|
8111
8240
|
let current = startDir;
|
|
8112
8241
|
for (let i = 0; i < 10; i++) {
|
|
8113
|
-
const candidate =
|
|
8114
|
-
if (
|
|
8242
|
+
const candidate = join17(current, "package.json");
|
|
8243
|
+
if (existsSync14(candidate)) {
|
|
8115
8244
|
try {
|
|
8116
8245
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8117
8246
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8121,7 +8250,7 @@ function readProducerVersion() {
|
|
|
8121
8250
|
} catch {
|
|
8122
8251
|
}
|
|
8123
8252
|
}
|
|
8124
|
-
const parent =
|
|
8253
|
+
const parent = dirname11(current);
|
|
8125
8254
|
if (parent === current) break;
|
|
8126
8255
|
current = parent;
|
|
8127
8256
|
}
|
|
@@ -8146,11 +8275,11 @@ var RenderChunkValidationError = class extends Error {
|
|
|
8146
8275
|
};
|
|
8147
8276
|
function createChunkVideoFrameInjectorFactory(frameLookup, options) {
|
|
8148
8277
|
if (!(frameLookup instanceof Promise))
|
|
8149
|
-
return () =>
|
|
8278
|
+
return () => createVideoFrameInjector3(frameLookup, options);
|
|
8150
8279
|
return () => {
|
|
8151
8280
|
let hook;
|
|
8152
8281
|
return async (page, time, heldVideoTime) => {
|
|
8153
|
-
hook ??=
|
|
8282
|
+
hook ??= createVideoFrameInjector3(await frameLookup, options);
|
|
8154
8283
|
await hook?.(page, time, heldVideoTime);
|
|
8155
8284
|
};
|
|
8156
8285
|
};
|
|
@@ -8164,8 +8293,8 @@ function frameNumberFromFileName(name) {
|
|
|
8164
8293
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
8165
8294
|
const result = [];
|
|
8166
8295
|
for (const v of videos) {
|
|
8167
|
-
const outputDir =
|
|
8168
|
-
if (!
|
|
8296
|
+
const outputDir = join18(planDir, "video-frames", v.videoId);
|
|
8297
|
+
if (!existsSync15(outputDir)) {
|
|
8169
8298
|
throw new Error(
|
|
8170
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.`
|
|
8171
8300
|
);
|
|
@@ -8183,7 +8312,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8183
8312
|
if (!frameName) continue;
|
|
8184
8313
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
8185
8314
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
8186
|
-
framePaths.set(frameIndex,
|
|
8315
|
+
framePaths.set(frameIndex, join18(outputDir, frameName));
|
|
8187
8316
|
}
|
|
8188
8317
|
result.push({
|
|
8189
8318
|
videoId: v.videoId,
|
|
@@ -8200,15 +8329,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8200
8329
|
}
|
|
8201
8330
|
return result;
|
|
8202
8331
|
}
|
|
8203
|
-
function contiguousRuns(sortedIndexes) {
|
|
8204
|
-
const runs2 = [];
|
|
8205
|
-
for (const index of sortedIndexes) {
|
|
8206
|
-
const last = runs2[runs2.length - 1];
|
|
8207
|
-
if (last && last.firstFrame + last.frames === index) last.frames++;
|
|
8208
|
-
else runs2.push({ firstFrame: index, frames: 1 });
|
|
8209
|
-
}
|
|
8210
|
-
return runs2;
|
|
8211
|
-
}
|
|
8212
8332
|
function buildChunkCaptureOptions(input) {
|
|
8213
8333
|
const { dimensions, encoder } = input;
|
|
8214
8334
|
const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
|
|
@@ -8231,28 +8351,19 @@ function buildChunkCaptureOptions(input) {
|
|
|
8231
8351
|
requiresWebGpu: compositionRequiresWebGpu3(input.html)
|
|
8232
8352
|
};
|
|
8233
8353
|
}
|
|
8234
|
-
function
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
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
|
|
8246
8366
|
);
|
|
8247
|
-
for (const frame of frames) {
|
|
8248
|
-
const globalTime = frame * fps.den / fps.num;
|
|
8249
|
-
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
8250
|
-
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
8251
|
-
indexes.add(payload.frameIndex);
|
|
8252
|
-
shown.set(videoId, indexes);
|
|
8253
|
-
}
|
|
8254
|
-
}
|
|
8255
|
-
return shown;
|
|
8256
8367
|
}
|
|
8257
8368
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
8258
8369
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
@@ -8269,49 +8380,32 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8269
8380
|
url: remoteSources[path].split("?")[0]
|
|
8270
8381
|
});
|
|
8271
8382
|
}
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
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;
|
|
8282
8394
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8283
|
-
const sourcePath = remoteUrl ??
|
|
8284
|
-
if (remoteUrl === void 0 && !
|
|
8395
|
+
const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
|
|
8396
|
+
if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
|
|
8285
8397
|
throw new RenderChunkValidationError(
|
|
8286
8398
|
MISSING_PLAN_ARTIFACT,
|
|
8287
8399
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
8288
8400
|
);
|
|
8289
8401
|
}
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
outputDir: input.outputRoot,
|
|
8298
|
-
format: range.format,
|
|
8299
|
-
frameRanges: contiguousRuns(indexes),
|
|
8300
|
-
shownBox: input.shownBoxes?.get(video.videoId)
|
|
8301
|
-
},
|
|
8302
|
-
void 0,
|
|
8303
|
-
input.cfg
|
|
8304
|
-
);
|
|
8305
|
-
const missing = indexes.find((index) => !extracted.framePaths.has(index));
|
|
8306
|
-
if (missing !== void 0) {
|
|
8307
|
-
throw new RenderChunkValidationError(
|
|
8308
|
-
INVALID_VIDEO_METADATA,
|
|
8309
|
-
`[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
|
|
8310
|
-
);
|
|
8311
|
-
}
|
|
8312
|
-
return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
|
|
8313
|
-
})
|
|
8314
|
-
);
|
|
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
|
+
});
|
|
8315
8409
|
}
|
|
8316
8410
|
|
|
8317
8411
|
// src/services/distributed/planFrameCost.ts
|
|
@@ -8329,12 +8423,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8329
8423
|
planVideos,
|
|
8330
8424
|
frames: sampled,
|
|
8331
8425
|
fps: fpsRatio,
|
|
8332
|
-
outputRoot:
|
|
8426
|
+
outputRoot: join19(workDir, "frame-cost-video-frames"),
|
|
8333
8427
|
cfg: input.cfg
|
|
8334
8428
|
})
|
|
8335
8429
|
];
|
|
8336
|
-
const lookup = extracted.length > 0 ?
|
|
8337
|
-
const compiledDir =
|
|
8430
|
+
const lookup = extracted.length > 0 ? createFrameLookupTable4(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
8431
|
+
const compiledDir = join19(planDir, "compiled");
|
|
8338
8432
|
const fileServer = await createFileServer({
|
|
8339
8433
|
projectDir: compiledDir,
|
|
8340
8434
|
compiledDir,
|
|
@@ -8346,7 +8440,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8346
8440
|
try {
|
|
8347
8441
|
session = await createCaptureSession9(
|
|
8348
8442
|
fileServer.url,
|
|
8349
|
-
|
|
8443
|
+
join19(workDir, "frame-cost"),
|
|
8350
8444
|
buildChunkCaptureOptions({
|
|
8351
8445
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
8352
8446
|
encoder: input,
|
|
@@ -8443,7 +8537,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
8443
8537
|
}
|
|
8444
8538
|
|
|
8445
8539
|
// src/services/render/planReferenceBackground.ts
|
|
8446
|
-
import { join as
|
|
8540
|
+
import { join as join20 } from "node:path";
|
|
8447
8541
|
import {
|
|
8448
8542
|
closeCaptureSession as closeCaptureSession10,
|
|
8449
8543
|
createCaptureSession as createCaptureSession10,
|
|
@@ -8456,14 +8550,14 @@ async function readPlanReferenceBackground(input) {
|
|
|
8456
8550
|
try {
|
|
8457
8551
|
server = await createFileServer({
|
|
8458
8552
|
projectDir: input.projectDir,
|
|
8459
|
-
compiledDir:
|
|
8553
|
+
compiledDir: join20(input.workDir, "compiled"),
|
|
8460
8554
|
port: 0,
|
|
8461
8555
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8462
8556
|
fps: input.options.fps
|
|
8463
8557
|
});
|
|
8464
8558
|
session = await createCaptureSession10(
|
|
8465
8559
|
server.url,
|
|
8466
|
-
|
|
8560
|
+
join20(input.workDir, "reference"),
|
|
8467
8561
|
{
|
|
8468
8562
|
...input.options,
|
|
8469
8563
|
requiresWebGpu: false
|
|
@@ -8499,8 +8593,8 @@ async function readPlanReferenceBackground(input) {
|
|
|
8499
8593
|
}
|
|
8500
8594
|
|
|
8501
8595
|
// src/services/render/stages/freezePlan.ts
|
|
8502
|
-
import { existsSync as
|
|
8503
|
-
import { join as
|
|
8596
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8597
|
+
import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8504
8598
|
import {
|
|
8505
8599
|
REFERENCE_BACKGROUND_FIELD,
|
|
8506
8600
|
referenceBackgroundResult as referenceBackgroundResult3
|
|
@@ -8693,7 +8787,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8693
8787
|
return sha256;
|
|
8694
8788
|
}
|
|
8695
8789
|
async function prehashPlanFiles(dir, digests) {
|
|
8696
|
-
if (!
|
|
8790
|
+
if (!existsSync16(dir)) return;
|
|
8697
8791
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8698
8792
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8699
8793
|
}
|
|
@@ -8718,7 +8812,7 @@ function listPlanFiles(planDir) {
|
|
|
8718
8812
|
function walk(dir) {
|
|
8719
8813
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
8720
8814
|
for (const entry of entries) {
|
|
8721
|
-
const full =
|
|
8815
|
+
const full = join21(dir, entry.name);
|
|
8722
8816
|
if (entry.isDirectory()) {
|
|
8723
8817
|
walk(full);
|
|
8724
8818
|
} else if (entry.isFile()) {
|
|
@@ -8759,19 +8853,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8759
8853
|
function writePlanRemoteFiles(planDir, files) {
|
|
8760
8854
|
if (files.length === 0) return;
|
|
8761
8855
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8762
|
-
|
|
8856
|
+
writeFileSync6(join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8763
8857
|
}
|
|
8764
8858
|
function readPlanRemoteFiles(planDir) {
|
|
8765
|
-
const path =
|
|
8766
|
-
return
|
|
8859
|
+
const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8860
|
+
return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8767
8861
|
}
|
|
8768
8862
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8769
|
-
const planJsonPath =
|
|
8770
|
-
const encoderJsonPath =
|
|
8771
|
-
if (!
|
|
8863
|
+
const planJsonPath = join21(planDir, "plan.json");
|
|
8864
|
+
const encoderJsonPath = join21(planDir, "meta", "encoder.json");
|
|
8865
|
+
if (!existsSync16(planJsonPath)) {
|
|
8772
8866
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8773
8867
|
}
|
|
8774
|
-
if (!
|
|
8868
|
+
if (!existsSync16(encoderJsonPath)) {
|
|
8775
8869
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8776
8870
|
}
|
|
8777
8871
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8801,21 +8895,21 @@ async function freezePlan(input) {
|
|
|
8801
8895
|
hasAudio,
|
|
8802
8896
|
digests
|
|
8803
8897
|
} = input;
|
|
8804
|
-
if (!
|
|
8898
|
+
if (!existsSync16(planDir)) {
|
|
8805
8899
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8806
8900
|
}
|
|
8807
|
-
const metaDir =
|
|
8808
|
-
if (!
|
|
8809
|
-
|
|
8810
|
-
|
|
8901
|
+
const metaDir = join21(planDir, "meta");
|
|
8902
|
+
if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8903
|
+
writeFileSync6(
|
|
8904
|
+
join21(metaDir, "composition.json"),
|
|
8811
8905
|
`${JSON.stringify(composition, null, 2)}
|
|
8812
8906
|
`,
|
|
8813
8907
|
"utf-8"
|
|
8814
8908
|
);
|
|
8815
8909
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8816
8910
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8817
|
-
|
|
8818
|
-
|
|
8911
|
+
writeFileSync6(join21(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8912
|
+
writeFileSync6(join21(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8819
8913
|
`, "utf-8");
|
|
8820
8914
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8821
8915
|
const planHash = computePlanHash({
|
|
@@ -8840,8 +8934,8 @@ async function freezePlan(input) {
|
|
|
8840
8934
|
duration: durationSeconds,
|
|
8841
8935
|
hasAudio
|
|
8842
8936
|
};
|
|
8843
|
-
const planJsonPath =
|
|
8844
|
-
|
|
8937
|
+
const planJsonPath = join21(planDir, "plan.json");
|
|
8938
|
+
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8845
8939
|
`, "utf-8");
|
|
8846
8940
|
return { planJsonPath, planHash };
|
|
8847
8941
|
}
|
|
@@ -8882,16 +8976,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8882
8976
|
}
|
|
8883
8977
|
|
|
8884
8978
|
// src/services/distributed/chunkAudio.ts
|
|
8885
|
-
import { closeSync as closeSync4, existsSync as
|
|
8886
|
-
import { writeFileSync as
|
|
8887
|
-
import { join as
|
|
8979
|
+
import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8980
|
+
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8981
|
+
import { join as join23 } from "node:path";
|
|
8888
8982
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8889
8983
|
|
|
8890
8984
|
// src/services/distributed/mp4Stitch.ts
|
|
8891
8985
|
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
|
|
8892
8986
|
import { open } from "node:fs/promises";
|
|
8893
8987
|
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
8894
|
-
import { dirname as
|
|
8988
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
8895
8989
|
import { finished, pipeline } from "node:stream/promises";
|
|
8896
8990
|
function boxAt(src, off, at, limit) {
|
|
8897
8991
|
let size = src.readUInt32BE(off);
|
|
@@ -9255,15 +9349,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
9255
9349
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
9256
9350
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
9257
9351
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
9258
|
-
mkdirSync8(
|
|
9352
|
+
mkdirSync8(dirname12(outputPath), { recursive: true });
|
|
9259
9353
|
const workDir = mkdtempSync2(`${outputPath}.stitch-`);
|
|
9260
|
-
const out = createWriteStream(
|
|
9354
|
+
const out = createWriteStream(join22(workDir, "out.mp4"));
|
|
9261
9355
|
try {
|
|
9262
9356
|
out.write(header);
|
|
9263
9357
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
9264
9358
|
out.end();
|
|
9265
9359
|
await finished(out);
|
|
9266
|
-
renameWithRetry7(
|
|
9360
|
+
renameWithRetry7(join22(workDir, "out.mp4"), outputPath);
|
|
9267
9361
|
} finally {
|
|
9268
9362
|
out.destroy();
|
|
9269
9363
|
rmSync6(workDir, { recursive: true, force: true });
|
|
@@ -9357,7 +9451,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9357
9451
|
});
|
|
9358
9452
|
const bytes = readFileSync8(input.audioPath);
|
|
9359
9453
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
9360
|
-
mkdirSync9(
|
|
9454
|
+
mkdirSync9(join23(input.planDir, SLICE_DIR), { recursive: true });
|
|
9361
9455
|
input.chunks.forEach((_, index) => {
|
|
9362
9456
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
9363
9457
|
if (!cut) return;
|
|
@@ -9365,17 +9459,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
9365
9459
|
const start = offsets[cut.firstPacket];
|
|
9366
9460
|
layout.partStarts[index] = start;
|
|
9367
9461
|
const end = offsets[last] + track.sizes[last];
|
|
9368
|
-
|
|
9462
|
+
writeFileSync7(join23(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
9369
9463
|
});
|
|
9370
9464
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
9371
|
-
|
|
9372
|
-
|
|
9465
|
+
writeFileSync7(join23(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
9466
|
+
writeFileSync7(join23(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
9373
9467
|
}
|
|
9374
9468
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9375
|
-
const layoutPath =
|
|
9376
|
-
if (!
|
|
9469
|
+
const layoutPath = join23(planDir, SLICE_LAYOUT);
|
|
9470
|
+
if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9377
9471
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9378
|
-
const frame = readFileSync8(
|
|
9472
|
+
const frame = readFileSync8(join23(planDir, SLICE_FRAME));
|
|
9379
9473
|
const fd = openSync4(scratchPath, "w");
|
|
9380
9474
|
try {
|
|
9381
9475
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -9388,7 +9482,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
9388
9482
|
);
|
|
9389
9483
|
const start = layout.partStarts[chunkIndex];
|
|
9390
9484
|
if (start !== void 0) {
|
|
9391
|
-
const part = readFileSync8(
|
|
9485
|
+
const part = readFileSync8(join23(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
9392
9486
|
writeSync(fd, part, 0, part.length, start);
|
|
9393
9487
|
}
|
|
9394
9488
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -9444,7 +9538,7 @@ async function muxChunkWithAudio(input) {
|
|
|
9444
9538
|
// src/services/distributed/planSize.ts
|
|
9445
9539
|
import { createHash as createHash7 } from "node:crypto";
|
|
9446
9540
|
import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
|
|
9447
|
-
import { extname as extname3, join as
|
|
9541
|
+
import { extname as extname3, join as join24, relative as relative4 } from "node:path";
|
|
9448
9542
|
var MAX_TOP_COMPONENTS = 10;
|
|
9449
9543
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9450
9544
|
".aac",
|
|
@@ -9556,7 +9650,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
9556
9650
|
return;
|
|
9557
9651
|
}
|
|
9558
9652
|
for (const entry of entries) {
|
|
9559
|
-
const full =
|
|
9653
|
+
const full = join24(dir, entry.name);
|
|
9560
9654
|
if (entry.isDirectory()) {
|
|
9561
9655
|
walkRegularFiles(full, visit);
|
|
9562
9656
|
} else if (entry.isFile()) {
|
|
@@ -9772,13 +9866,13 @@ function assertPositiveInteger(name, value) {
|
|
|
9772
9866
|
}
|
|
9773
9867
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
9774
9868
|
"plan.json",
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9869
|
+
join25("meta", "composition.json"),
|
|
9870
|
+
join25("meta", "encoder.json"),
|
|
9871
|
+
join25("meta", "chunks.json")
|
|
9778
9872
|
];
|
|
9779
9873
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
9780
9874
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
9781
|
-
rmSync7(
|
|
9875
|
+
rmSync7(join25(planDir, relativePath), { force: true });
|
|
9782
9876
|
}
|
|
9783
9877
|
}
|
|
9784
9878
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -9933,19 +10027,19 @@ function createVideoSourceShipper(input) {
|
|
|
9933
10027
|
const known = shipped.get(srcPath);
|
|
9934
10028
|
if (known) return known;
|
|
9935
10029
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
9936
|
-
const current = stagedRel === null ? srcPath :
|
|
10030
|
+
const current = stagedRel === null ? srcPath : join25(input.planDir, "video-frames", stagedRel);
|
|
9937
10031
|
let result = null;
|
|
9938
10032
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9939
10033
|
const rel = pathInside(root, srcPath);
|
|
9940
|
-
if (rel !== null && (
|
|
10034
|
+
if (rel !== null && (existsSync18(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9941
10035
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9942
10036
|
break;
|
|
9943
10037
|
}
|
|
9944
10038
|
}
|
|
9945
10039
|
if (result === null) {
|
|
9946
10040
|
result = `video-sources/${shipped.size}${extname4(current)}`;
|
|
9947
|
-
const destination =
|
|
9948
|
-
mkdirSync10(
|
|
10041
|
+
const destination = join25(input.planDir, ...result.split("/"));
|
|
10042
|
+
mkdirSync10(join25(input.planDir, "video-sources"), { recursive: true });
|
|
9949
10043
|
try {
|
|
9950
10044
|
linkSync(current, destination);
|
|
9951
10045
|
} catch {
|
|
@@ -9965,7 +10059,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9965
10059
|
useGpu: false,
|
|
9966
10060
|
browserGpuMode: "software"
|
|
9967
10061
|
});
|
|
9968
|
-
if (!
|
|
10062
|
+
if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9969
10063
|
const log = config.logger ?? defaultLogger;
|
|
9970
10064
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9971
10065
|
const abortSignal = config.abortSignal;
|
|
@@ -9994,14 +10088,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9994
10088
|
variables: config.variables
|
|
9995
10089
|
});
|
|
9996
10090
|
const entryFile = config.entryFile ?? "index.html";
|
|
9997
|
-
const htmlPath =
|
|
9998
|
-
if (!
|
|
10091
|
+
const htmlPath = join25(projectDir, entryFile);
|
|
10092
|
+
if (!existsSync18(htmlPath)) {
|
|
9999
10093
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
10000
10094
|
}
|
|
10001
|
-
const workDir =
|
|
10095
|
+
const workDir = join25(planDir, ".plan-work");
|
|
10002
10096
|
rmSync7(workDir, { recursive: true, force: true });
|
|
10003
10097
|
mkdirSync10(workDir, { recursive: true });
|
|
10004
|
-
const compiledDir =
|
|
10098
|
+
const compiledDir = join25(workDir, "compiled");
|
|
10005
10099
|
mkdirSync10(compiledDir, { recursive: true });
|
|
10006
10100
|
let stageStarted = Date.now();
|
|
10007
10101
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -10015,10 +10109,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10015
10109
|
}
|
|
10016
10110
|
});
|
|
10017
10111
|
logPlanStage(log, "copy", stageStarted);
|
|
10018
|
-
const finalCompiledDir =
|
|
10112
|
+
const finalCompiledDir = join25(planDir, "compiled");
|
|
10019
10113
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
10020
10114
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
10021
|
-
const remoteLocalPath = (rel) =>
|
|
10115
|
+
const remoteLocalPath = (rel) => join25(projectDir, ...rel.split("/"));
|
|
10022
10116
|
for (const [, file] of remoteFiles) {
|
|
10023
10117
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
10024
10118
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -10039,7 +10133,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10039
10133
|
urlByAbsentLocalPath,
|
|
10040
10134
|
projectDir,
|
|
10041
10135
|
workDir,
|
|
10042
|
-
htmlPath,
|
|
10043
10136
|
entryFile,
|
|
10044
10137
|
job,
|
|
10045
10138
|
cfg,
|
|
@@ -10222,17 +10315,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10222
10315
|
let stageStarted = Date.now();
|
|
10223
10316
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
10224
10317
|
logPlanStage(log, "extract", stageStarted);
|
|
10225
|
-
const stagedVideoFrames =
|
|
10226
|
-
const videoFramesDst =
|
|
10227
|
-
if (
|
|
10228
|
-
const videoSourcesDir =
|
|
10318
|
+
const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
|
|
10319
|
+
const videoFramesDst = join25(planDir, "video-frames");
|
|
10320
|
+
if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10321
|
+
const videoSourcesDir = join25(planDir, "video-sources");
|
|
10229
10322
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10230
|
-
if (
|
|
10323
|
+
if (existsSync18(stagedVideoFrames)) {
|
|
10231
10324
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10232
10325
|
} else {
|
|
10233
10326
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10234
10327
|
}
|
|
10235
|
-
if (
|
|
10328
|
+
if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10236
10329
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10237
10330
|
const shipVideoSource = createVideoSourceShipper({
|
|
10238
10331
|
planDir,
|
|
@@ -10262,7 +10355,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10262
10355
|
};
|
|
10263
10356
|
})
|
|
10264
10357
|
});
|
|
10265
|
-
mkdirSync10(
|
|
10358
|
+
mkdirSync10(join25(planDir, "meta"), { recursive: true });
|
|
10266
10359
|
writePlanRemoteFiles(
|
|
10267
10360
|
planDir,
|
|
10268
10361
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -10271,8 +10364,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10271
10364
|
sizeBytes
|
|
10272
10365
|
}))
|
|
10273
10366
|
);
|
|
10274
|
-
|
|
10275
|
-
|
|
10367
|
+
writeFileSync8(
|
|
10368
|
+
join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
10276
10369
|
JSON.stringify(planVideosJson, null, 2),
|
|
10277
10370
|
"utf-8"
|
|
10278
10371
|
);
|
|
@@ -10351,11 +10444,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10351
10444
|
if (audioResult?.audioError) {
|
|
10352
10445
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10353
10446
|
}
|
|
10354
|
-
const planAudioPath =
|
|
10355
|
-
if (audioResult?.hasAudio &&
|
|
10447
|
+
const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10448
|
+
if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
|
|
10356
10449
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10357
10450
|
}
|
|
10358
|
-
if (chunksCarryAudio &&
|
|
10451
|
+
if (chunksCarryAudio && existsSync18(planAudioPath)) {
|
|
10359
10452
|
if (options.sliceChunkAudio) {
|
|
10360
10453
|
stageStarted = Date.now();
|
|
10361
10454
|
await writeChunkAudioSlices({
|
|
@@ -10468,7 +10561,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10468
10561
|
if (mixed.audioError) {
|
|
10469
10562
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10470
10563
|
}
|
|
10471
|
-
if (!mixed.hasAudio || !
|
|
10564
|
+
if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10472
10565
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10473
10566
|
return { hasAudio: true };
|
|
10474
10567
|
} finally {
|
|
@@ -10483,7 +10576,7 @@ import {
|
|
|
10483
10576
|
} from "@hyperframes/engine";
|
|
10484
10577
|
import {
|
|
10485
10578
|
copyFileSync as copyFileSync8,
|
|
10486
|
-
existsSync as
|
|
10579
|
+
existsSync as existsSync20,
|
|
10487
10580
|
lstatSync as lstatSync2,
|
|
10488
10581
|
mkdirSync as mkdirSync12,
|
|
10489
10582
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10491,26 +10584,26 @@ import {
|
|
|
10491
10584
|
readdirSync as readdirSync7,
|
|
10492
10585
|
rmSync as rmSync9,
|
|
10493
10586
|
statSync as statSync7,
|
|
10494
|
-
writeFileSync as
|
|
10587
|
+
writeFileSync as writeFileSync10
|
|
10495
10588
|
} from "node:fs";
|
|
10496
10589
|
import { tmpdir } from "node:os";
|
|
10497
|
-
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";
|
|
10498
10591
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
10499
|
-
import { createFrameLookupTable as
|
|
10592
|
+
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
10500
10593
|
|
|
10501
10594
|
// src/services/distributed/planV2Publisher.ts
|
|
10502
10595
|
import {
|
|
10503
10596
|
copyFileSync as copyFileSync7,
|
|
10504
|
-
existsSync as
|
|
10597
|
+
existsSync as existsSync19,
|
|
10505
10598
|
linkSync as linkSync2,
|
|
10506
10599
|
mkdirSync as mkdirSync11,
|
|
10507
10600
|
mkdtempSync as mkdtempSync3,
|
|
10508
10601
|
rmSync as rmSync8,
|
|
10509
10602
|
statSync as statSync6,
|
|
10510
|
-
writeFileSync as
|
|
10603
|
+
writeFileSync as writeFileSync9
|
|
10511
10604
|
} from "node:fs";
|
|
10512
10605
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
10513
|
-
import { dirname as
|
|
10606
|
+
import { dirname as dirname13, join as join27 } from "node:path";
|
|
10514
10607
|
|
|
10515
10608
|
// src/services/distributed/planV2Errors.ts
|
|
10516
10609
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -10525,7 +10618,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
10525
10618
|
};
|
|
10526
10619
|
|
|
10527
10620
|
// src/services/distributed/planV2Layout.ts
|
|
10528
|
-
import { join as
|
|
10621
|
+
import { join as join26 } from "node:path";
|
|
10529
10622
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
10530
10623
|
function assertPlanV2Sha256(value, field) {
|
|
10531
10624
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -10535,7 +10628,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
10535
10628
|
}
|
|
10536
10629
|
function planV2BlobPath(root, sha256) {
|
|
10537
10630
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
10538
|
-
return
|
|
10631
|
+
return join26(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
10539
10632
|
}
|
|
10540
10633
|
|
|
10541
10634
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -10570,13 +10663,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10570
10663
|
#linkFile;
|
|
10571
10664
|
#committed = false;
|
|
10572
10665
|
constructor(destinationDir, options = {}) {
|
|
10573
|
-
if (
|
|
10666
|
+
if (existsSync19(destinationDir)) {
|
|
10574
10667
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10575
10668
|
}
|
|
10576
10669
|
this.destinationDir = destinationDir;
|
|
10577
10670
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
10578
|
-
mkdirSync11(
|
|
10579
|
-
this.temporaryDir = mkdtempSync3(
|
|
10671
|
+
mkdirSync11(dirname13(destinationDir), { recursive: true });
|
|
10672
|
+
this.temporaryDir = mkdtempSync3(join27(dirname13(destinationDir), ".plan-v2-publish-"));
|
|
10580
10673
|
}
|
|
10581
10674
|
async putBlob(blob) {
|
|
10582
10675
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -10587,10 +10680,10 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10587
10680
|
);
|
|
10588
10681
|
}
|
|
10589
10682
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10590
|
-
if (
|
|
10591
|
-
mkdirSync11(
|
|
10592
|
-
const stagingDir = mkdtempSync3(
|
|
10593
|
-
const temporaryPath =
|
|
10683
|
+
if (existsSync19(destinationPath)) return;
|
|
10684
|
+
mkdirSync11(dirname13(destinationPath), { recursive: true });
|
|
10685
|
+
const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10686
|
+
const temporaryPath = join27(stagingDir, "blob");
|
|
10594
10687
|
try {
|
|
10595
10688
|
try {
|
|
10596
10689
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -10605,13 +10698,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10605
10698
|
}
|
|
10606
10699
|
async commitManifest(manifestBytes) {
|
|
10607
10700
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10608
|
-
if (!
|
|
10701
|
+
if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10609
10702
|
throw new PlanV2IntegrityError(
|
|
10610
10703
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10611
10704
|
);
|
|
10612
10705
|
}
|
|
10613
10706
|
}
|
|
10614
|
-
|
|
10707
|
+
writeFileSync9(join27(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10615
10708
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
10616
10709
|
this.#committed = true;
|
|
10617
10710
|
}
|
|
@@ -10656,7 +10749,7 @@ function listFiles(root) {
|
|
|
10656
10749
|
const rootResolved = resolve9(root);
|
|
10657
10750
|
function walk(dir) {
|
|
10658
10751
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
10659
|
-
const absolutePath =
|
|
10752
|
+
const absolutePath = join28(dir, entry.name);
|
|
10660
10753
|
if (entry.isDirectory()) {
|
|
10661
10754
|
walk(absolutePath);
|
|
10662
10755
|
} else if (entry.isFile()) {
|
|
@@ -10683,13 +10776,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10683
10776
|
}
|
|
10684
10777
|
}
|
|
10685
10778
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10686
|
-
const videoRoot =
|
|
10687
|
-
if (!
|
|
10779
|
+
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10780
|
+
if (!existsSync20(videoRoot)) return;
|
|
10688
10781
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10689
10782
|
if (!videoEntry.isDirectory()) continue;
|
|
10690
|
-
const videoDir =
|
|
10783
|
+
const videoDir = join28(videoRoot, videoEntry.name);
|
|
10691
10784
|
if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
10692
|
-
const sentinelPath =
|
|
10785
|
+
const sentinelPath = join28(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
10693
10786
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
10694
10787
|
}
|
|
10695
10788
|
}
|
|
@@ -10736,7 +10829,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10736
10829
|
if (video.deferredRange) {
|
|
10737
10830
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
10738
10831
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
10739
|
-
framePaths2.set(index,
|
|
10832
|
+
framePaths2.set(index, join28(outputDir, String(index)));
|
|
10740
10833
|
}
|
|
10741
10834
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
10742
10835
|
}
|
|
@@ -10744,7 +10837,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10744
10837
|
const framePaths = /* @__PURE__ */ new Map();
|
|
10745
10838
|
for (const frameName of frameNames) {
|
|
10746
10839
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
10747
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
10840
|
+
assertValidExtractionCacheCompleteSentinel(join28(outputDir, frameName));
|
|
10748
10841
|
continue;
|
|
10749
10842
|
}
|
|
10750
10843
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -10763,7 +10856,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10763
10856
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
10764
10857
|
);
|
|
10765
10858
|
}
|
|
10766
|
-
framePaths.set(frameIndex,
|
|
10859
|
+
framePaths.set(frameIndex, join28(outputDir, frameName));
|
|
10767
10860
|
}
|
|
10768
10861
|
return {
|
|
10769
10862
|
...video,
|
|
@@ -10784,8 +10877,8 @@ function parsePlanVideosJson2(value) {
|
|
|
10784
10877
|
}
|
|
10785
10878
|
}
|
|
10786
10879
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10787
|
-
const videosPath =
|
|
10788
|
-
if (!
|
|
10880
|
+
const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10881
|
+
if (!existsSync20(videosPath)) return;
|
|
10789
10882
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10790
10883
|
for (const video of videos.extracted) {
|
|
10791
10884
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10818,13 +10911,13 @@ function parseChunkSlices(value) {
|
|
|
10818
10911
|
});
|
|
10819
10912
|
}
|
|
10820
10913
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10821
|
-
const videoRoot =
|
|
10822
|
-
const hasExtractedFrames =
|
|
10823
|
-
const videosPath =
|
|
10824
|
-
if (!
|
|
10914
|
+
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10915
|
+
const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10916
|
+
const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10917
|
+
if (!existsSync20(videosPath)) {
|
|
10825
10918
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10826
10919
|
}
|
|
10827
|
-
const chunksPath =
|
|
10920
|
+
const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
|
|
10828
10921
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10829
10922
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
10830
10923
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -10832,7 +10925,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
|
10832
10925
|
const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
|
|
10833
10926
|
const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
|
|
10834
10927
|
const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
|
|
10835
|
-
const table =
|
|
10928
|
+
const table = createFrameLookupTable5(
|
|
10836
10929
|
parsedVideos.videos,
|
|
10837
10930
|
extracted,
|
|
10838
10931
|
resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
|
|
@@ -10878,10 +10971,10 @@ function computeManifestHash(manifest) {
|
|
|
10878
10971
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10879
10972
|
}
|
|
10880
10973
|
function writeBlob(sourcePath, destinationPath) {
|
|
10881
|
-
if (
|
|
10882
|
-
mkdirSync12(
|
|
10883
|
-
const temporaryDir = mkdtempSync4(
|
|
10884
|
-
const temporaryPath =
|
|
10974
|
+
if (existsSync20(destinationPath)) return;
|
|
10975
|
+
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
10976
|
+
const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
10977
|
+
const temporaryPath = join28(temporaryDir, "blob");
|
|
10885
10978
|
try {
|
|
10886
10979
|
copyFileSync8(sourcePath, temporaryPath);
|
|
10887
10980
|
renameWithRetry10(temporaryPath, destinationPath);
|
|
@@ -10917,8 +11010,8 @@ async function putTasks(tasks) {
|
|
|
10917
11010
|
if (failure) throw failure.reason;
|
|
10918
11011
|
}
|
|
10919
11012
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10920
|
-
const executionPlanPath =
|
|
10921
|
-
if (!
|
|
11013
|
+
const executionPlanPath = join28(executionPlanDir, "plan.json");
|
|
11014
|
+
if (!existsSync20(executionPlanPath)) {
|
|
10922
11015
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10923
11016
|
}
|
|
10924
11017
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10945,7 +11038,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10945
11038
|
}
|
|
10946
11039
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
10947
11040
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
10948
|
-
const encoder = readJsonFile(
|
|
11041
|
+
const encoder = readJsonFile(join28(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
10949
11042
|
const files = listFiles(executionPlanDir);
|
|
10950
11043
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
10951
11044
|
for (const file of files) {
|
|
@@ -10998,21 +11091,21 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10998
11091
|
};
|
|
10999
11092
|
}
|
|
11000
11093
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
11001
|
-
if (
|
|
11094
|
+
if (existsSync20(planV2Dir)) {
|
|
11002
11095
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11003
11096
|
}
|
|
11004
11097
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
11005
11098
|
if (publication.storedBlobs.length > 0) {
|
|
11006
11099
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
11007
11100
|
}
|
|
11008
|
-
mkdirSync12(
|
|
11009
|
-
const tempDir = mkdtempSync4(
|
|
11101
|
+
mkdirSync12(dirname14(planV2Dir), { recursive: true });
|
|
11102
|
+
const tempDir = mkdtempSync4(join28(dirname14(planV2Dir), ".plan-v2-build-"));
|
|
11010
11103
|
try {
|
|
11011
11104
|
for (const blob of publication.blobs) {
|
|
11012
11105
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
11013
11106
|
}
|
|
11014
|
-
|
|
11015
|
-
|
|
11107
|
+
writeFileSync10(
|
|
11108
|
+
join28(tempDir, "plan.json"),
|
|
11016
11109
|
canonicalJsonStringify(publication.manifest),
|
|
11017
11110
|
"utf-8"
|
|
11018
11111
|
);
|
|
@@ -11053,7 +11146,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
|
|
|
11053
11146
|
}
|
|
11054
11147
|
}
|
|
11055
11148
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
11056
|
-
const stagingRoot = mkdtempSync4(
|
|
11149
|
+
const stagingRoot = mkdtempSync4(join28(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
11057
11150
|
const digests = /* @__PURE__ */ new Map();
|
|
11058
11151
|
try {
|
|
11059
11152
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -11064,7 +11157,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11064
11157
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
11065
11158
|
whileAudioMixes: () => putBlobs(
|
|
11066
11159
|
publisher,
|
|
11067
|
-
listFiles(
|
|
11160
|
+
listFiles(join28(stagingRoot, "compiled")).map(
|
|
11068
11161
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
11069
11162
|
)
|
|
11070
11163
|
),
|
|
@@ -11084,12 +11177,12 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11084
11177
|
}
|
|
11085
11178
|
}
|
|
11086
11179
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11087
|
-
if (
|
|
11180
|
+
if (existsSync20(planV2Dir)) {
|
|
11088
11181
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11089
11182
|
}
|
|
11090
11183
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
11091
11184
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
11092
|
-
stagingParentDir:
|
|
11185
|
+
stagingParentDir: dirname14(planV2Dir)
|
|
11093
11186
|
});
|
|
11094
11187
|
return resultFromManifest(planV2Dir, manifest);
|
|
11095
11188
|
}
|
|
@@ -11101,7 +11194,7 @@ function resultFromManifest(planV2Dir, manifest) {
|
|
|
11101
11194
|
isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
|
|
11102
11195
|
),
|
|
11103
11196
|
planDir: planV2Dir,
|
|
11104
|
-
manifestPath:
|
|
11197
|
+
manifestPath: join28(planV2Dir, "plan.json"),
|
|
11105
11198
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
11106
11199
|
planHash: manifest.planHash,
|
|
11107
11200
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -11237,8 +11330,8 @@ function parsePlanV2Manifest(value) {
|
|
|
11237
11330
|
return parsed;
|
|
11238
11331
|
}
|
|
11239
11332
|
function readPlanV2Manifest(planV2Dir) {
|
|
11240
|
-
const manifestPath =
|
|
11241
|
-
if (!
|
|
11333
|
+
const manifestPath = join28(planV2Dir, "plan.json");
|
|
11334
|
+
if (!existsSync20(manifestPath)) {
|
|
11242
11335
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11243
11336
|
}
|
|
11244
11337
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11257,7 +11350,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11257
11350
|
}
|
|
11258
11351
|
function verifyBlob(planV2Dir, artifact) {
|
|
11259
11352
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11260
|
-
if (!
|
|
11353
|
+
if (!existsSync20(sourcePath)) {
|
|
11261
11354
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11262
11355
|
}
|
|
11263
11356
|
const stats = statSync7(sourcePath);
|
|
@@ -11270,7 +11363,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11270
11363
|
return sourcePath;
|
|
11271
11364
|
}
|
|
11272
11365
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11273
|
-
if (
|
|
11366
|
+
if (existsSync20(destinationDir)) {
|
|
11274
11367
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11275
11368
|
}
|
|
11276
11369
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11282,17 +11375,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11282
11375
|
artifact,
|
|
11283
11376
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
11284
11377
|
}));
|
|
11285
|
-
mkdirSync12(
|
|
11286
|
-
const tempDir = mkdtempSync4(
|
|
11378
|
+
mkdirSync12(dirname14(destinationDir), { recursive: true });
|
|
11379
|
+
const tempDir = mkdtempSync4(join28(dirname14(destinationDir), ".plan-v2-materialize-"));
|
|
11287
11380
|
try {
|
|
11288
11381
|
for (const { artifact, sourcePath } of verified) {
|
|
11289
|
-
const destinationPath =
|
|
11290
|
-
mkdirSync12(
|
|
11382
|
+
const destinationPath = join28(tempDir, ...artifact.path.split("/"));
|
|
11383
|
+
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
11291
11384
|
copyFileSync8(sourcePath, destinationPath);
|
|
11292
11385
|
}
|
|
11293
11386
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
11294
|
-
|
|
11295
|
-
|
|
11387
|
+
writeFileSync10(
|
|
11388
|
+
join28(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
11296
11389
|
canonicalJsonStringify({
|
|
11297
11390
|
manifest,
|
|
11298
11391
|
target,
|
|
@@ -11315,7 +11408,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11315
11408
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
11316
11409
|
// before the container change carries the legacy name, and materializing it
|
|
11317
11410
|
// under the new one would point at a file that was never written.
|
|
11318
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
11411
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join28(destinationDir, audioArtifact.path) : null : null
|
|
11319
11412
|
};
|
|
11320
11413
|
}
|
|
11321
11414
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -11334,8 +11427,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11334
11427
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
11335
11428
|
}
|
|
11336
11429
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11337
|
-
const markerPath =
|
|
11338
|
-
if (!
|
|
11430
|
+
const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11431
|
+
if (!existsSync20(markerPath)) return null;
|
|
11339
11432
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11340
11433
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11341
11434
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11352,8 +11445,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11352
11445
|
assertRemoteSources(remoteSources, required);
|
|
11353
11446
|
for (const artifact of required) {
|
|
11354
11447
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11355
|
-
const path =
|
|
11356
|
-
if (!
|
|
11448
|
+
const path = join28(planDir, ...artifact.path.split("/"));
|
|
11449
|
+
if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11357
11450
|
throw new PlanV2IntegrityError(
|
|
11358
11451
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11359
11452
|
);
|
|
@@ -11368,7 +11461,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11368
11461
|
// src/services/distributed/planV2Execution.ts
|
|
11369
11462
|
import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
|
|
11370
11463
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11371
|
-
import { join as
|
|
11464
|
+
import { join as join31 } from "node:path";
|
|
11372
11465
|
|
|
11373
11466
|
// src/services/distributed/assemble.ts
|
|
11374
11467
|
import {
|
|
@@ -11377,16 +11470,16 @@ import {
|
|
|
11377
11470
|
} from "@hyperframes/engine";
|
|
11378
11471
|
import {
|
|
11379
11472
|
cpSync as cpSync3,
|
|
11380
|
-
existsSync as
|
|
11473
|
+
existsSync as existsSync21,
|
|
11381
11474
|
mkdirSync as mkdirSync13,
|
|
11382
11475
|
mkdtempSync as mkdtempSync5,
|
|
11383
11476
|
readFileSync as readFileSync10,
|
|
11384
11477
|
readdirSync as readdirSync8,
|
|
11385
11478
|
rmSync as rmSync10,
|
|
11386
11479
|
statSync as statSync8,
|
|
11387
|
-
writeFileSync as
|
|
11480
|
+
writeFileSync as writeFileSync11
|
|
11388
11481
|
} from "node:fs";
|
|
11389
|
-
import { dirname as
|
|
11482
|
+
import { dirname as dirname15, join as join29 } from "node:path";
|
|
11390
11483
|
import {
|
|
11391
11484
|
appendRateCapArgs as appendRateCapArgs2,
|
|
11392
11485
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
@@ -11432,8 +11525,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11432
11525
|
}
|
|
11433
11526
|
const format = plan.dimensions.format;
|
|
11434
11527
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11435
|
-
if (!
|
|
11436
|
-
mkdirSync13(
|
|
11528
|
+
if (!existsSync21(dirname15(outputPath))) {
|
|
11529
|
+
mkdirSync13(dirname15(outputPath), { recursive: true });
|
|
11437
11530
|
}
|
|
11438
11531
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
11439
11532
|
const fps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
@@ -11455,8 +11548,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11455
11548
|
};
|
|
11456
11549
|
try {
|
|
11457
11550
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11458
|
-
const finalPath =
|
|
11459
|
-
const audio = audioPath !== null &&
|
|
11551
|
+
const finalPath = join29(workDir, `output.${format}`);
|
|
11552
|
+
const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
|
|
11460
11553
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11461
11554
|
renameWithRetry11(finalPath, outputPath);
|
|
11462
11555
|
log.info("[assemble] stage timings", {
|
|
@@ -11474,7 +11567,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11474
11567
|
});
|
|
11475
11568
|
}
|
|
11476
11569
|
}
|
|
11477
|
-
const fileSize =
|
|
11570
|
+
const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
|
|
11478
11571
|
return {
|
|
11479
11572
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11480
11573
|
outputPath,
|
|
@@ -11484,15 +11577,15 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11484
11577
|
};
|
|
11485
11578
|
}
|
|
11486
11579
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11487
|
-
const planJsonPath =
|
|
11488
|
-
const chunksJsonPath =
|
|
11489
|
-
if (!
|
|
11580
|
+
const planJsonPath = join29(planDir, "plan.json");
|
|
11581
|
+
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11582
|
+
if (!existsSync21(planJsonPath)) {
|
|
11490
11583
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11491
11584
|
}
|
|
11492
11585
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11493
11586
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11494
11587
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11495
|
-
if (!
|
|
11588
|
+
if (!existsSync21(chunksJsonPath)) {
|
|
11496
11589
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11497
11590
|
}
|
|
11498
11591
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11507,7 +11600,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11507
11600
|
}
|
|
11508
11601
|
}
|
|
11509
11602
|
for (const path of chunkPaths.flat()) {
|
|
11510
|
-
if (!
|
|
11603
|
+
if (!existsSync21(path)) {
|
|
11511
11604
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11512
11605
|
}
|
|
11513
11606
|
}
|
|
@@ -11536,7 +11629,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
11536
11629
|
return args;
|
|
11537
11630
|
}
|
|
11538
11631
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
11539
|
-
const cfrOutputPath =
|
|
11632
|
+
const cfrOutputPath = join29(ctx.workDir, `cfr.${ctx.format}`);
|
|
11540
11633
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
11541
11634
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
11542
11635
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -11563,7 +11656,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11563
11656
|
);
|
|
11564
11657
|
return;
|
|
11565
11658
|
}
|
|
11566
|
-
const paddedAudioPath =
|
|
11659
|
+
const paddedAudioPath = join29(ctx.workDir, "audio-padded.m4a");
|
|
11567
11660
|
const padTrimResult = await ctx.timed(
|
|
11568
11661
|
"audioMs",
|
|
11569
11662
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -11603,9 +11696,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11603
11696
|
}
|
|
11604
11697
|
}
|
|
11605
11698
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
11606
|
-
const listPath =
|
|
11699
|
+
const listPath = join29(workDir, "concat-list.txt");
|
|
11607
11700
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
11608
|
-
|
|
11701
|
+
writeFileSync11(listPath, `${body}
|
|
11609
11702
|
`, "utf-8");
|
|
11610
11703
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
11611
11704
|
}
|
|
@@ -11641,12 +11734,12 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11641
11734
|
return lockedAudio(planDir).separateAudio === true;
|
|
11642
11735
|
}
|
|
11643
11736
|
function planFormat(planDir) {
|
|
11644
|
-
const plan = JSON.parse(readFileSync10(
|
|
11737
|
+
const plan = JSON.parse(readFileSync10(join29(planDir, "plan.json"), "utf-8"));
|
|
11645
11738
|
return plan.dimensions.format;
|
|
11646
11739
|
}
|
|
11647
11740
|
function lockedAudio(planDir) {
|
|
11648
|
-
const encoderJsonPath =
|
|
11649
|
-
if (!
|
|
11741
|
+
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11742
|
+
if (!existsSync21(encoderJsonPath)) return {};
|
|
11650
11743
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11651
11744
|
}
|
|
11652
11745
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11655,8 +11748,8 @@ function readCfrEncoder(planDir, format) {
|
|
|
11655
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.`
|
|
11656
11749
|
);
|
|
11657
11750
|
}
|
|
11658
|
-
const encoderJsonPath =
|
|
11659
|
-
if (!
|
|
11751
|
+
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11752
|
+
if (!existsSync21(encoderJsonPath)) {
|
|
11660
11753
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11661
11754
|
}
|
|
11662
11755
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11668,7 +11761,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11668
11761
|
return encoderJson;
|
|
11669
11762
|
}
|
|
11670
11763
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11671
|
-
if (
|
|
11764
|
+
if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11672
11765
|
mkdirSync13(outputPath, { recursive: true });
|
|
11673
11766
|
let globalIdx = 0;
|
|
11674
11767
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11682,8 +11775,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11682
11775
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
11683
11776
|
}
|
|
11684
11777
|
for (const frame of frames) {
|
|
11685
|
-
const dst =
|
|
11686
|
-
cpSync3(
|
|
11778
|
+
const dst = join29(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
11779
|
+
cpSync3(join29(chunkDir, frame), dst);
|
|
11687
11780
|
globalIdx += 1;
|
|
11688
11781
|
}
|
|
11689
11782
|
}
|
|
@@ -11692,14 +11785,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11692
11785
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11693
11786
|
);
|
|
11694
11787
|
}
|
|
11695
|
-
if (audioPath !== null &&
|
|
11696
|
-
const sidecar =
|
|
11788
|
+
if (audioPath !== null && existsSync21(audioPath)) {
|
|
11789
|
+
const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11697
11790
|
cpSync3(audioPath, sidecar);
|
|
11698
11791
|
}
|
|
11699
11792
|
let fileSize = 0;
|
|
11700
11793
|
for (const name of readdirSync8(outputPath)) {
|
|
11701
11794
|
try {
|
|
11702
|
-
fileSize += statSync8(
|
|
11795
|
+
fileSize += statSync8(join29(outputPath, name)).size;
|
|
11703
11796
|
} catch {
|
|
11704
11797
|
}
|
|
11705
11798
|
}
|
|
@@ -11713,9 +11806,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11713
11806
|
|
|
11714
11807
|
// src/services/distributed/renderChunk.ts
|
|
11715
11808
|
import { randomBytes } from "node:crypto";
|
|
11716
|
-
import { existsSync as
|
|
11809
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11717
11810
|
import { cpus } from "node:os";
|
|
11718
|
-
import { join as
|
|
11811
|
+
import { join as join30 } from "node:path";
|
|
11719
11812
|
import {
|
|
11720
11813
|
assertSwiftShader,
|
|
11721
11814
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -11723,7 +11816,7 @@ import {
|
|
|
11723
11816
|
classifyCaptureFailure as classifyCaptureFailure4,
|
|
11724
11817
|
closeCaptureSession as closeCaptureSession11,
|
|
11725
11818
|
createCaptureSession as createCaptureSession11,
|
|
11726
|
-
createFrameLookupTable as
|
|
11819
|
+
createFrameLookupTable as createFrameLookupTable6,
|
|
11727
11820
|
deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
|
|
11728
11821
|
getEncoderPreset as getEncoderPreset6,
|
|
11729
11822
|
initializeSession as initializeSession10,
|
|
@@ -11839,7 +11932,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
11839
11932
|
if (kind === "file") return sha256Hex(readFileSync11(outputPath));
|
|
11840
11933
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11841
11934
|
const lines = entries.map(
|
|
11842
|
-
(name) => `${name}\0${sha256Hex(readFileSync11(
|
|
11935
|
+
(name) => `${name}\0${sha256Hex(readFileSync11(join30(outputPath, name)))}`
|
|
11843
11936
|
);
|
|
11844
11937
|
return sha256Hex(lines.join("\0"));
|
|
11845
11938
|
}
|
|
@@ -11856,10 +11949,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
11856
11949
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
11857
11950
|
const start = Date.now();
|
|
11858
11951
|
const log = defaultLogger;
|
|
11859
|
-
const planJsonPath =
|
|
11860
|
-
const encoderJsonPath =
|
|
11861
|
-
const chunksJsonPath =
|
|
11862
|
-
if (!
|
|
11952
|
+
const planJsonPath = join30(planDir, "plan.json");
|
|
11953
|
+
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
11954
|
+
const chunksJsonPath = join30(planDir, "meta", "chunks.json");
|
|
11955
|
+
if (!existsSync22(planJsonPath)) {
|
|
11863
11956
|
throw new RenderChunkValidationError(
|
|
11864
11957
|
MISSING_PLAN_ARTIFACT,
|
|
11865
11958
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11875,7 +11968,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11875
11968
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11876
11969
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11877
11970
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11878
|
-
if (!
|
|
11971
|
+
if (!existsSync22(required)) {
|
|
11879
11972
|
throw new RenderChunkValidationError(
|
|
11880
11973
|
MISSING_PLAN_ARTIFACT,
|
|
11881
11974
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11884,9 +11977,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11884
11977
|
}
|
|
11885
11978
|
const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
11886
11979
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11887
|
-
const videosJsonPath =
|
|
11980
|
+
const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11888
11981
|
let planVideos = null;
|
|
11889
|
-
if (
|
|
11982
|
+
if (existsSync22(videosJsonPath)) {
|
|
11890
11983
|
try {
|
|
11891
11984
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11892
11985
|
} catch (err) {
|
|
@@ -11916,8 +12009,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11916
12009
|
}
|
|
11917
12010
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11918
12011
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11919
|
-
const compiledDir =
|
|
11920
|
-
if (!
|
|
12012
|
+
const compiledDir = join30(planDir, "compiled");
|
|
12013
|
+
if (!existsSync22(compiledDir)) {
|
|
11921
12014
|
throw new RenderChunkValidationError(
|
|
11922
12015
|
MISSING_PLAN_ARTIFACT,
|
|
11923
12016
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -11974,13 +12067,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11974
12067
|
};
|
|
11975
12068
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
11976
12069
|
mkdirSync14(workDir, { recursive: true });
|
|
11977
|
-
const framesDir =
|
|
12070
|
+
const framesDir = join30(workDir, "captured-frames");
|
|
11978
12071
|
mkdirSync14(framesDir, { recursive: true });
|
|
11979
12072
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
11980
12073
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
11981
12074
|
const chunkFps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
|
|
11982
12075
|
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
11983
|
-
const shownFrames = planVideos ?
|
|
12076
|
+
const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
11984
12077
|
const remoteVideoIds = new Set(
|
|
11985
12078
|
planVideos?.extracted.filter(
|
|
11986
12079
|
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
@@ -11999,7 +12092,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11999
12092
|
dimensions: plan.dimensions,
|
|
12000
12093
|
encoder,
|
|
12001
12094
|
videoCount: planVideos?.videos.length ?? 0,
|
|
12002
|
-
html: readFileSync11(
|
|
12095
|
+
html: readFileSync11(join30(compiledDir, "index.html"), "utf-8"),
|
|
12003
12096
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
12004
12097
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
12005
12098
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -12134,7 +12227,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12134
12227
|
deferredVideoFrames.catch(() => {
|
|
12135
12228
|
});
|
|
12136
12229
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
|
|
12137
|
-
(deferred) =>
|
|
12230
|
+
(deferred) => createFrameLookupTable6(
|
|
12138
12231
|
planVideos.videos,
|
|
12139
12232
|
[
|
|
12140
12233
|
...rebuildExtractedFramesFromPlanDir(
|
|
@@ -12167,10 +12260,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12167
12260
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12168
12261
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12169
12262
|
if (isPngSequence) {
|
|
12170
|
-
if (!
|
|
12263
|
+
if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12171
12264
|
} else {
|
|
12172
|
-
const outDir =
|
|
12173
|
-
if (!
|
|
12265
|
+
const outDir = join30(outputChunkPath, "..");
|
|
12266
|
+
if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12174
12267
|
}
|
|
12175
12268
|
const encodeInput = {
|
|
12176
12269
|
job,
|
|
@@ -12380,7 +12473,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12380
12473
|
producerVersion: plan.producerVersion,
|
|
12381
12474
|
ffmpegVersion
|
|
12382
12475
|
};
|
|
12383
|
-
|
|
12476
|
+
writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
12384
12477
|
`, "utf-8");
|
|
12385
12478
|
try {
|
|
12386
12479
|
rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -12413,8 +12506,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12413
12506
|
|
|
12414
12507
|
// src/services/distributed/planV2Execution.ts
|
|
12415
12508
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
12416
|
-
const workRoot = mkdtempSync6(
|
|
12417
|
-
const materializedPlanDir =
|
|
12509
|
+
const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-chunk-"));
|
|
12510
|
+
const materializedPlanDir = join31(workRoot, "plan");
|
|
12418
12511
|
try {
|
|
12419
12512
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
12420
12513
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
@@ -12423,8 +12516,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
|
12423
12516
|
}
|
|
12424
12517
|
}
|
|
12425
12518
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
12426
|
-
const workRoot = mkdtempSync6(
|
|
12427
|
-
const materializedPlanDir =
|
|
12519
|
+
const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-assembler-"));
|
|
12520
|
+
const materializedPlanDir = join31(workRoot, "plan");
|
|
12428
12521
|
try {
|
|
12429
12522
|
const materialized = materializePlanV2Target(
|
|
12430
12523
|
planV2Dir,
|
|
@@ -12716,7 +12809,7 @@ function validateIntDimension(field, value) {
|
|
|
12716
12809
|
// src/services/distributed/projectHash.ts
|
|
12717
12810
|
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
|
|
12718
12811
|
import { createHash as createHash8 } from "node:crypto";
|
|
12719
|
-
import { join as
|
|
12812
|
+
import { join as join32 } from "node:path";
|
|
12720
12813
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
12721
12814
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
12722
12815
|
function listSiteFiles(projectDir) {
|
|
@@ -12725,7 +12818,7 @@ function listSiteFiles(projectDir) {
|
|
|
12725
12818
|
function walk(dir, rel) {
|
|
12726
12819
|
for (const name of readdirSync10(dir).sort()) {
|
|
12727
12820
|
if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
|
|
12728
|
-
const full =
|
|
12821
|
+
const full = join32(dir, name);
|
|
12729
12822
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
12730
12823
|
let stat;
|
|
12731
12824
|
try {
|
|
@@ -12755,7 +12848,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
|
12755
12848
|
for (const rel of files) {
|
|
12756
12849
|
hash.update(rel);
|
|
12757
12850
|
hash.update("\0");
|
|
12758
|
-
const fd = openSync5(
|
|
12851
|
+
const fd = openSync5(join32(projectDir, rel), "r");
|
|
12759
12852
|
try {
|
|
12760
12853
|
let read;
|
|
12761
12854
|
while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
|
|
@@ -12797,7 +12890,7 @@ var isMissing = (result) => result.error?.code === "ENOENT";
|
|
|
12797
12890
|
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
|
|
12798
12891
|
|
|
12799
12892
|
// src/services/distributed/probeFacts.ts
|
|
12800
|
-
import { writeFileSync as
|
|
12893
|
+
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
12801
12894
|
import { extname as extname6 } from "node:path";
|
|
12802
12895
|
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
12803
12896
|
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -12806,7 +12899,7 @@ function wantsProbeFacts(path) {
|
|
|
12806
12899
|
}
|
|
12807
12900
|
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
12808
12901
|
try {
|
|
12809
|
-
|
|
12902
|
+
writeFileSync13(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
12810
12903
|
return "built";
|
|
12811
12904
|
} catch (error) {
|
|
12812
12905
|
if (signal?.aborted) throw error;
|