@hyperframes/producer 0.8.225 → 0.8.227
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 +63 -45
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +67 -67
- package/dist/index.js +308 -140
- package/dist/public-server.js +223 -55
- package/dist/services/render/perfSummary.d.ts +5 -1
- package/dist/services/render/stages/extractVideosStage.d.ts +2 -1
- package/dist/services/render/videoLayerPassthroughPlan.d.ts +30 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// src/services/renderOrchestrator.ts
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
|
-
existsSync as
|
|
4
|
+
existsSync as existsSync17,
|
|
5
5
|
mkdirSync as mkdirSync10,
|
|
6
|
-
readFileSync as readFileSync9,
|
|
7
6
|
readdirSync as readdirSync5,
|
|
8
7
|
rmSync as rmSync10,
|
|
9
8
|
statSync as statSync8,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
25
24
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
26
25
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
26
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
27
27
|
import {
|
|
28
28
|
extractMediaMetadata as extractMediaMetadata4,
|
|
29
29
|
resolveConfig as resolveConfig2,
|
|
@@ -2134,6 +2134,14 @@ function pushWorkerDedupPerfs(results, sink) {
|
|
|
2134
2134
|
if (r.perf) sink.push(r.perf);
|
|
2135
2135
|
}
|
|
2136
2136
|
}
|
|
2137
|
+
function capturedBeyondViewport(perfs, fallback) {
|
|
2138
|
+
const used = perfs.flatMap((perf) => perf.captureBeyondViewport ?? []);
|
|
2139
|
+
return used.length > 0 ? used.includes(true) : fallback;
|
|
2140
|
+
}
|
|
2141
|
+
function withCapturedBeyondViewport(capture, perfs) {
|
|
2142
|
+
const used = capturedBeyondViewport(perfs, capture.captureBeyondViewport);
|
|
2143
|
+
return used === void 0 ? capture : { ...capture, captureBeyondViewport: used };
|
|
2144
|
+
}
|
|
2137
2145
|
function pushWorkerPageErrors(results, sink) {
|
|
2138
2146
|
for (const result of results) {
|
|
2139
2147
|
for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
|
|
@@ -5766,7 +5774,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
5766
5774
|
// src/services/htmlCompiler.ts
|
|
5767
5775
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5768
5776
|
import { createHash as createHash6 } from "node:crypto";
|
|
5769
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7
|
|
5777
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5770
5778
|
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5771
5779
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5772
5780
|
import {
|
|
@@ -5819,7 +5827,8 @@ import {
|
|
|
5819
5827
|
} from "@hyperframes/parsers/sub-composition-validity";
|
|
5820
5828
|
import {
|
|
5821
5829
|
isUnresolvedAssetPlaceholder,
|
|
5822
|
-
readProjectFile
|
|
5830
|
+
readProjectFile,
|
|
5831
|
+
readTextFile
|
|
5823
5832
|
} from "@hyperframes/parsers/asset-resolution";
|
|
5824
5833
|
|
|
5825
5834
|
// src/services/renderMediaCollector.ts
|
|
@@ -7908,11 +7917,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
7908
7917
|
return document2.toString();
|
|
7909
7918
|
}
|
|
7910
7919
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
7911
|
-
const entryHtml = rebaseDirectEntryAssetPaths(
|
|
7912
|
-
readFileSync7(htmlPath, "utf-8"),
|
|
7913
|
-
projectDir,
|
|
7914
|
-
htmlPath
|
|
7915
|
-
);
|
|
7920
|
+
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
7916
7921
|
const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
|
|
7917
7922
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
7918
7923
|
const probes = /* @__PURE__ */ new Map();
|
|
@@ -8763,7 +8768,7 @@ async function runProbeStage(input) {
|
|
|
8763
8768
|
);
|
|
8764
8769
|
const hasVariableMedia = hasVariableBoundMedia(compiled.html, job.config.variables);
|
|
8765
8770
|
const hasInsertedMedia = hasRuntimeMediaChanges(compiled.html);
|
|
8766
|
-
const needsBrowser = probeRequiresBrowser({
|
|
8771
|
+
const needsBrowser = cfg.videoLayerPassthrough || probeRequiresBrowser({
|
|
8767
8772
|
durationSeconds: composition.duration,
|
|
8768
8773
|
unresolvedCompositionCount: compiled.unresolvedCompositions.length,
|
|
8769
8774
|
hasAutoStart,
|
|
@@ -9395,17 +9400,23 @@ function resolveVideoExtractionPolicy(env = process.env) {
|
|
|
9395
9400
|
const maxTransientRetries = failureMode !== "off" && env.HF_VIDEO_EXTRACTION_MAX_RETRIES?.trim() === "1" ? 1 : 0;
|
|
9396
9401
|
return { failureMode, maxTransientRetries };
|
|
9397
9402
|
}
|
|
9403
|
+
var MAX_NAMED_SOURCES = 5;
|
|
9398
9404
|
var VideoExtractionStageError = class extends Error {
|
|
9399
9405
|
constructor(code, retryable, failures, extractionFailure = buildExtractionFailureMetadata(
|
|
9400
9406
|
failures.map((failure) => ({
|
|
9401
9407
|
kind: failure.kind,
|
|
9402
9408
|
affectedElementCount: failure.count
|
|
9403
9409
|
}))
|
|
9404
|
-
), ffmpegReason) {
|
|
9410
|
+
), ffmpegReason, sources = []) {
|
|
9405
9411
|
const total = failures.reduce((sum2, failure) => sum2 + failure.count, 0);
|
|
9406
9412
|
const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
|
|
9407
9413
|
const reason = ffmpegReason ? `: ffmpeg said: ${ffmpegReason}` : "";
|
|
9408
|
-
|
|
9414
|
+
const shown = sources.slice(0, MAX_NAMED_SOURCES).join(", ");
|
|
9415
|
+
const more = sources.length > MAX_NAMED_SOURCES ? ` and ${sources.length - MAX_NAMED_SOURCES} more` : "";
|
|
9416
|
+
const files = sources.length > 0 ? ` (${shown}${more})` : "";
|
|
9417
|
+
super(
|
|
9418
|
+
`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]${files}${reason}`
|
|
9419
|
+
);
|
|
9409
9420
|
this.code = code;
|
|
9410
9421
|
this.retryable = retryable;
|
|
9411
9422
|
this.failures = failures;
|
|
@@ -9453,7 +9464,10 @@ function buildVideoExtractionStageError(result) {
|
|
|
9453
9464
|
retryable,
|
|
9454
9465
|
failures,
|
|
9455
9466
|
extractionFailureMetadataFromResult(result),
|
|
9456
|
-
result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason
|
|
9467
|
+
result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason,
|
|
9468
|
+
[
|
|
9469
|
+
...new Set(result.errors.flatMap((failure) => failure.source ? [failure.source] : []))
|
|
9470
|
+
].sort()
|
|
9457
9471
|
);
|
|
9458
9472
|
}
|
|
9459
9473
|
function buildHdrProbeStageError(failures) {
|
|
@@ -9532,7 +9546,8 @@ async function runExtractVideosStage(input) {
|
|
|
9532
9546
|
assertNotAborted,
|
|
9533
9547
|
materializeSymlinks,
|
|
9534
9548
|
deferRangeExtraction,
|
|
9535
|
-
urlByAbsentLocalPath
|
|
9549
|
+
urlByAbsentLocalPath,
|
|
9550
|
+
skipFrameExtractionIds = []
|
|
9536
9551
|
} = input;
|
|
9537
9552
|
const stage2Start = Date.now();
|
|
9538
9553
|
const extractionPolicy = resolveVideoExtractionPolicy();
|
|
@@ -9603,7 +9618,10 @@ async function runExtractVideosStage(input) {
|
|
|
9603
9618
|
);
|
|
9604
9619
|
imageColorSpaces.push(...probed);
|
|
9605
9620
|
}
|
|
9606
|
-
if (
|
|
9621
|
+
if (skipFrameExtractionIds.length > 0) {
|
|
9622
|
+
await appendVideoAudioFromProbe({ projectDir, compiledDir, composition, urlByAbsentLocalPath });
|
|
9623
|
+
videoReadinessSkipIds = [...skipFrameExtractionIds];
|
|
9624
|
+
} else if (composition.videos.length > 0) {
|
|
9607
9625
|
const totalVideos = composition.videos.length;
|
|
9608
9626
|
for (let i = 0; i < totalVideos; i++) {
|
|
9609
9627
|
const v = composition.videos[i];
|
|
@@ -9699,6 +9717,116 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
9699
9717
|
existingAudioSrcs.add(video.src);
|
|
9700
9718
|
}
|
|
9701
9719
|
}
|
|
9720
|
+
async function appendVideoAudioFromProbe(input) {
|
|
9721
|
+
const { projectDir, compiledDir, composition, urlByAbsentLocalPath } = input;
|
|
9722
|
+
const videos = composition.videos.filter(
|
|
9723
|
+
(video) => !isVideoPastTimelineEnd(video, composition.duration)
|
|
9724
|
+
);
|
|
9725
|
+
const probed = await Promise.all(
|
|
9726
|
+
videos.map(async (video) => {
|
|
9727
|
+
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
9728
|
+
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
9729
|
+
if (!isHttpUrl2(readFrom) && !existsSync11(readFrom)) return null;
|
|
9730
|
+
const metadata = await extractMediaMetadata3(readFrom);
|
|
9731
|
+
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
9732
|
+
readFrom,
|
|
9733
|
+
video,
|
|
9734
|
+
metadata,
|
|
9735
|
+
resolveVideoExtractionWindow2(video, metadata, composition.duration)
|
|
9736
|
+
);
|
|
9737
|
+
if (window2.durationSeconds <= 0) return null;
|
|
9738
|
+
rebaseVideoToWindow(video, window2);
|
|
9739
|
+
return { videoId: video.id, metadata };
|
|
9740
|
+
})
|
|
9741
|
+
);
|
|
9742
|
+
appendAutoDetectedVideoAudio(
|
|
9743
|
+
composition,
|
|
9744
|
+
probed.filter((entry) => entry !== null)
|
|
9745
|
+
);
|
|
9746
|
+
}
|
|
9747
|
+
|
|
9748
|
+
// src/services/render/videoLayerPassthroughPlan.ts
|
|
9749
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
9750
|
+
import {
|
|
9751
|
+
buildVideoLayerInput,
|
|
9752
|
+
isHttpUrl as isHttpUrl3,
|
|
9753
|
+
qualifyVideoLayer,
|
|
9754
|
+
resolveProjectRelativeSrc as resolveProjectRelativeSrc4
|
|
9755
|
+
} from "@hyperframes/engine";
|
|
9756
|
+
var SAMPLES = 9;
|
|
9757
|
+
function inspectVideoLayout(id, w, h, time) {
|
|
9758
|
+
window.__hf?.seek?.(time);
|
|
9759
|
+
const video = document.getElementById(id);
|
|
9760
|
+
if (!video) return { reason: "video_missing" };
|
|
9761
|
+
const r = video.getBoundingClientRect();
|
|
9762
|
+
if (Math.abs(r.left) > 0.5 || Math.abs(r.top) > 0.5) return { reason: "not_full_canvas" };
|
|
9763
|
+
if (Math.abs(r.width - w) > 0.5 || Math.abs(r.height - h) > 0.5) {
|
|
9764
|
+
return { reason: "not_full_canvas" };
|
|
9765
|
+
}
|
|
9766
|
+
const painted = (s) => s.backgroundColor !== "" && s.backgroundColor !== "transparent" && s.backgroundColor !== "rgba(0, 0, 0, 0)" || s.backgroundImage !== "" && s.backgroundImage !== "none";
|
|
9767
|
+
for (let e = video; e && e !== document.documentElement; ) {
|
|
9768
|
+
const s = getComputedStyle(e);
|
|
9769
|
+
if (s.opacity !== "1") return { reason: "opacity" };
|
|
9770
|
+
if (s.transform !== "none" || s.filter !== "none") return { reason: "transform_or_filter" };
|
|
9771
|
+
if (s.clipPath !== "none" || s.maskImage !== "none") return { reason: "clip_or_mask" };
|
|
9772
|
+
if (s.mixBlendMode !== "normal") return { reason: "blend_mode" };
|
|
9773
|
+
if (s.visibility !== "visible" || s.display === "none") return { reason: "not_shown" };
|
|
9774
|
+
const cleared = e === document.body || e.hasAttribute("data-composition-id");
|
|
9775
|
+
if (e !== video && painted(s) && !cleared) return { reason: "ancestor_background" };
|
|
9776
|
+
e = e.parentElement;
|
|
9777
|
+
}
|
|
9778
|
+
for (const root of Array.from(document.querySelectorAll("[data-composition-id]"))) {
|
|
9779
|
+
if (root.parentElement?.closest("[data-composition-id]") && painted(getComputedStyle(root))) {
|
|
9780
|
+
return { reason: "subcomposition_background" };
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
const fitStyle = getComputedStyle(video).objectFit;
|
|
9784
|
+
if (fitStyle !== "fill" && fitStyle !== "cover") return { reason: "object_fit" };
|
|
9785
|
+
const own = Number.parseInt(getComputedStyle(video).zIndex, 10) || 0;
|
|
9786
|
+
for (const other of Array.from(document.body.querySelectorAll("*"))) {
|
|
9787
|
+
if (other === video || other.contains(video) || video.contains(other)) continue;
|
|
9788
|
+
if (other.tagName === "SCRIPT" || other.tagName === "STYLE") continue;
|
|
9789
|
+
const z = Number.parseInt(getComputedStyle(other).zIndex, 10) || 0;
|
|
9790
|
+
const before = video.compareDocumentPosition(other) & Node.DOCUMENT_POSITION_PRECEDING;
|
|
9791
|
+
if (z < own || z === own && before) {
|
|
9792
|
+
const box2 = other.getBoundingClientRect();
|
|
9793
|
+
if (box2.width > 0 && box2.height > 0) return { reason: "paints_under" };
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
return { fit: fitStyle === "cover" ? "cover" : "fill" };
|
|
9797
|
+
}
|
|
9798
|
+
async function probeVideoLayout(page, videoId, canvas, duration) {
|
|
9799
|
+
let fit = "fill";
|
|
9800
|
+
for (let i = 0; i < SAMPLES; i++) {
|
|
9801
|
+
const t = duration * i / SAMPLES;
|
|
9802
|
+
const seen = await page.evaluate(inspectVideoLayout, videoId, canvas.width, canvas.height, t);
|
|
9803
|
+
if ("reason" in seen) return seen;
|
|
9804
|
+
if (seen.fit === "cover") fit = "cover";
|
|
9805
|
+
}
|
|
9806
|
+
await page.evaluate(
|
|
9807
|
+
() => window.__hf?.seek?.(0)
|
|
9808
|
+
);
|
|
9809
|
+
return { fit };
|
|
9810
|
+
}
|
|
9811
|
+
async function planVideoLayerPassthrough(input) {
|
|
9812
|
+
const q = qualifyVideoLayer(input.videos, input.duration);
|
|
9813
|
+
if (!q.qualifies) return { reason: q.reason };
|
|
9814
|
+
const { video } = q;
|
|
9815
|
+
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
9816
|
+
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
9817
|
+
if (!existsSync12(path)) return { reason: "source_missing" };
|
|
9818
|
+
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
9819
|
+
if ("reason" in layout) return layout;
|
|
9820
|
+
const layer = await buildVideoLayerInput(
|
|
9821
|
+
path,
|
|
9822
|
+
video.mediaStart,
|
|
9823
|
+
input.duration,
|
|
9824
|
+
input.fps,
|
|
9825
|
+
input.canvas,
|
|
9826
|
+
layout.fit
|
|
9827
|
+
);
|
|
9828
|
+
return layer ? { videoId: video.id, layer } : { reason: "source_unsupported" };
|
|
9829
|
+
}
|
|
9702
9830
|
|
|
9703
9831
|
// src/services/render/stages/audioStage.ts
|
|
9704
9832
|
import { join as join20 } from "node:path";
|
|
@@ -10310,7 +10438,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10310
10438
|
}
|
|
10311
10439
|
|
|
10312
10440
|
// src/services/render/stages/captureStage.ts
|
|
10313
|
-
import { existsSync as
|
|
10441
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10314
10442
|
import { join as join21 } from "node:path";
|
|
10315
10443
|
import {
|
|
10316
10444
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10381,7 +10509,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10381
10509
|
}
|
|
10382
10510
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10383
10511
|
let total = sampleDirectoryBytes(framesDir);
|
|
10384
|
-
if (!
|
|
10512
|
+
if (!existsSync13(workDir)) return total;
|
|
10385
10513
|
for (const attempt of readdirSync3(workDir)) {
|
|
10386
10514
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10387
10515
|
total += sampleDirectoryBytes(join21(workDir, attempt));
|
|
@@ -10684,7 +10812,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10684
10812
|
}
|
|
10685
10813
|
|
|
10686
10814
|
// src/services/render/stages/captureHdrStage.ts
|
|
10687
|
-
import { existsSync as
|
|
10815
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10688
10816
|
import { join as join25 } from "node:path";
|
|
10689
10817
|
import {
|
|
10690
10818
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -10930,7 +11058,7 @@ import { Worker } from "node:worker_threads";
|
|
|
10930
11058
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
10931
11059
|
import { dirname as dirname9, join as join23 } from "node:path";
|
|
10932
11060
|
import { createRequire } from "node:module";
|
|
10933
|
-
import { existsSync as
|
|
11061
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10934
11062
|
import { cpus as cpus2 } from "node:os";
|
|
10935
11063
|
function resolveWorkerEntry(explicit) {
|
|
10936
11064
|
if (explicit && explicit.length > 0) {
|
|
@@ -10943,7 +11071,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
10943
11071
|
}
|
|
10944
11072
|
const moduleDir = dirname9(fileURLToPath3(import.meta.url));
|
|
10945
11073
|
const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
|
|
10946
|
-
if (
|
|
11074
|
+
if (existsSync14(jsPath)) return { path: jsPath, isTs: false };
|
|
10947
11075
|
const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
|
|
10948
11076
|
return { path: tsPath, isTs: true };
|
|
10949
11077
|
}
|
|
@@ -11504,7 +11632,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11504
11632
|
}
|
|
11505
11633
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11506
11634
|
const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
|
|
11507
|
-
if (debugDumpDir && !
|
|
11635
|
+
if (debugDumpDir && !existsSync15(debugDumpDir)) {
|
|
11508
11636
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11509
11637
|
}
|
|
11510
11638
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -11661,10 +11789,10 @@ async function runCaptureHdrStage(input) {
|
|
|
11661
11789
|
// src/services/render/stages/encodeStage.ts
|
|
11662
11790
|
import {
|
|
11663
11791
|
copyFileSync as copyFileSync4,
|
|
11664
|
-
existsSync as
|
|
11792
|
+
existsSync as existsSync16,
|
|
11665
11793
|
mkdirSync as mkdirSync9,
|
|
11666
11794
|
readdirSync as readdirSync4,
|
|
11667
|
-
readFileSync as
|
|
11795
|
+
readFileSync as readFileSync7,
|
|
11668
11796
|
rmSync as rmSync9,
|
|
11669
11797
|
statSync as statSync7,
|
|
11670
11798
|
writeFileSync as writeFileSync5
|
|
@@ -11758,7 +11886,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
11758
11886
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
11759
11887
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11760
11888
|
}
|
|
11761
|
-
const gif =
|
|
11889
|
+
const gif = readFileSync7(outputPath);
|
|
11762
11890
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
11763
11891
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
11764
11892
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -11791,11 +11919,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11791
11919
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11792
11920
|
}
|
|
11793
11921
|
}
|
|
11794
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
11922
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
|
|
11795
11923
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
11796
11924
|
if (failure) return failure;
|
|
11797
11925
|
}
|
|
11798
|
-
const fileSize =
|
|
11926
|
+
const fileSize = existsSync16(outputPath) ? statSync7(outputPath).size : 0;
|
|
11799
11927
|
return {
|
|
11800
11928
|
success: true,
|
|
11801
11929
|
outputPath,
|
|
@@ -11956,7 +12084,7 @@ async function runEncodeStage(input) {
|
|
|
11956
12084
|
const stage5Start = Date.now();
|
|
11957
12085
|
if (isPngSequence) {
|
|
11958
12086
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
11959
|
-
if (!
|
|
12087
|
+
if (!existsSync16(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
11960
12088
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
11961
12089
|
if (captured.length === 0) {
|
|
11962
12090
|
throw new Error(
|
|
@@ -11967,7 +12095,7 @@ async function runEncodeStage(input) {
|
|
|
11967
12095
|
const dst = join26(outputPath, formatExportFrameName(i, "png"));
|
|
11968
12096
|
copyFileSync4(join26(framesDir, name), dst);
|
|
11969
12097
|
});
|
|
11970
|
-
if (hasAudio && audioOutputPath &&
|
|
12098
|
+
if (hasAudio && audioOutputPath && existsSync16(audioOutputPath)) {
|
|
11971
12099
|
copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
|
|
11972
12100
|
log.info(
|
|
11973
12101
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12348,7 +12476,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12348
12476
|
let rangeStart = null;
|
|
12349
12477
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12350
12478
|
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12351
|
-
const missing = !
|
|
12479
|
+
const missing = !existsSync17(framePath) || statSync8(framePath).size <= 8;
|
|
12352
12480
|
if (missing && rangeStart === null) {
|
|
12353
12481
|
rangeStart = frameIndex;
|
|
12354
12482
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12473,7 +12601,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12473
12601
|
let captured = 0;
|
|
12474
12602
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12475
12603
|
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12476
|
-
if (
|
|
12604
|
+
if (existsSync17(framePath)) captured++;
|
|
12477
12605
|
}
|
|
12478
12606
|
return captured;
|
|
12479
12607
|
}
|
|
@@ -13136,7 +13264,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13136
13264
|
validateDeliveryEncoderConfig(job.config);
|
|
13137
13265
|
const debugDir = resolveRenderDebugDir();
|
|
13138
13266
|
const outputDir = dirname11(outputPath);
|
|
13139
|
-
if (!
|
|
13267
|
+
if (!existsSync17(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13140
13268
|
const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13141
13269
|
const pipelineStart = Date.now();
|
|
13142
13270
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13277,7 +13405,7 @@ async function executeRenderPipeline(input) {
|
|
|
13277
13405
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13278
13406
|
assertNotAborted();
|
|
13279
13407
|
assertConfiguredFfmpegBinariesExist();
|
|
13280
|
-
if (!
|
|
13408
|
+
if (!existsSync17(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13281
13409
|
if (job.config.debug) {
|
|
13282
13410
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13283
13411
|
}
|
|
@@ -13307,21 +13435,21 @@ async function executeRenderPipeline(input) {
|
|
|
13307
13435
|
});
|
|
13308
13436
|
const entryFile = job.config.entryFile || "index.html";
|
|
13309
13437
|
let htmlPath = join27(projectDir, entryFile);
|
|
13310
|
-
if (!
|
|
13438
|
+
if (!existsSync17(htmlPath)) {
|
|
13311
13439
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13312
13440
|
}
|
|
13313
13441
|
assertNotAborted();
|
|
13314
|
-
const rawEntry =
|
|
13442
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
13315
13443
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13316
13444
|
const wrapperPath = join27(workDir, "standalone-entry.html");
|
|
13317
13445
|
const projectIndexPath = join27(projectDir, "index.html");
|
|
13318
|
-
if (!
|
|
13446
|
+
if (!existsSync17(projectIndexPath)) {
|
|
13319
13447
|
throw new Error(
|
|
13320
13448
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13321
13449
|
);
|
|
13322
13450
|
}
|
|
13323
13451
|
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
13324
|
-
|
|
13452
|
+
readTextFile2(projectIndexPath),
|
|
13325
13453
|
entryFile,
|
|
13326
13454
|
rawEntry
|
|
13327
13455
|
);
|
|
@@ -13484,6 +13612,42 @@ async function executeRenderPipeline(input) {
|
|
|
13484
13612
|
compositionHash,
|
|
13485
13613
|
beginFrameStalled: probeResult.beginFrameStalled
|
|
13486
13614
|
});
|
|
13615
|
+
let videoLayerPlan;
|
|
13616
|
+
if (cfg.videoLayerPassthrough) {
|
|
13617
|
+
const gates = {
|
|
13618
|
+
mp4: outputFormat === "mp4",
|
|
13619
|
+
software_encode: !job.config.useGpu,
|
|
13620
|
+
opaque_output: !needsAlpha,
|
|
13621
|
+
sdr: rendersSdrOnly(job.config, composition) || (job.config.hdrMode === void 0 || job.config.hdrMode === "auto") && composition.images.length === 0,
|
|
13622
|
+
streaming: shouldUseStreamingEncode(cfg, outputFormat, 1, probeResult.duration),
|
|
13623
|
+
probe_page: probeSession?.isInitialized === true
|
|
13624
|
+
};
|
|
13625
|
+
const failed = Object.entries(gates).find(([, ok]) => !ok)?.[0];
|
|
13626
|
+
if (failed || !probeSession) {
|
|
13627
|
+
log.info(`[Render] Video layer passthrough off: ${failed ?? "probe_page"}`);
|
|
13628
|
+
} else {
|
|
13629
|
+
videoLayerPlan = await planVideoLayerPassthrough({
|
|
13630
|
+
videos: composition.videos,
|
|
13631
|
+
duration: probeResult.duration,
|
|
13632
|
+
fps: job.config.fps,
|
|
13633
|
+
canvas: { width, height },
|
|
13634
|
+
page: probeSession.page,
|
|
13635
|
+
projectDir,
|
|
13636
|
+
compiledDir: join27(workDir, "compiled")
|
|
13637
|
+
});
|
|
13638
|
+
if (videoLayerPlan.videoId === void 0) {
|
|
13639
|
+
log.info(`[Render] Video layer passthrough off: ${videoLayerPlan.reason}`);
|
|
13640
|
+
} else {
|
|
13641
|
+
cfg.useDrawElement = false;
|
|
13642
|
+
cfg.forceScreenshot = true;
|
|
13643
|
+
captureForceScreenshot = true;
|
|
13644
|
+
const opened = probeSession;
|
|
13645
|
+
probeSession = null;
|
|
13646
|
+
await closeCaptureSession8(opened);
|
|
13647
|
+
}
|
|
13648
|
+
}
|
|
13649
|
+
}
|
|
13650
|
+
const videoLayer = videoLayerPlan?.videoId === void 0 ? void 0 : videoLayerPlan;
|
|
13487
13651
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
13488
13652
|
code: "extract_video"
|
|
13489
13653
|
});
|
|
@@ -13499,6 +13663,7 @@ async function executeRenderPipeline(input) {
|
|
|
13499
13663
|
cfg,
|
|
13500
13664
|
log,
|
|
13501
13665
|
composition,
|
|
13666
|
+
skipFrameExtractionIds: videoLayer ? [videoLayer.videoId] : [],
|
|
13502
13667
|
abortSignal: executionSignal,
|
|
13503
13668
|
assertNotAborted,
|
|
13504
13669
|
// Local staging can use links; distributed plan() alone requires real copies.
|
|
@@ -13617,7 +13782,7 @@ async function executeRenderPipeline(input) {
|
|
|
13617
13782
|
throw new Error("File server failed to initialize before frame capture");
|
|
13618
13783
|
}
|
|
13619
13784
|
const framesDir = join27(workDir, "captured-frames");
|
|
13620
|
-
if (!
|
|
13785
|
+
if (!existsSync17(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13621
13786
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13622
13787
|
code: "start_browsers"
|
|
13623
13788
|
});
|
|
@@ -13643,8 +13808,9 @@ async function executeRenderPipeline(input) {
|
|
|
13643
13808
|
width,
|
|
13644
13809
|
height,
|
|
13645
13810
|
fps: job.config.fps,
|
|
13646
|
-
format: captureImageFormat,
|
|
13647
|
-
transparent: needsAlpha,
|
|
13811
|
+
format: videoLayer ? "png" : captureImageFormat,
|
|
13812
|
+
transparent: needsAlpha || videoLayer !== void 0,
|
|
13813
|
+
...videoLayer ? { hiddenElementIds: [videoLayer.videoId] } : {},
|
|
13648
13814
|
quality: captureImageFormat === "png" ? void 0 : resolveCaptureJpegQuality(job.config.quality),
|
|
13649
13815
|
variables: job.config.variables,
|
|
13650
13816
|
deviceScaleFactor,
|
|
@@ -13941,6 +14107,7 @@ async function executeRenderPipeline(input) {
|
|
|
13941
14107
|
}
|
|
13942
14108
|
}
|
|
13943
14109
|
);
|
|
14110
|
+
if (videoLayer) workerCount = 1;
|
|
13944
14111
|
const preRoutingWorkerCount = workerCount;
|
|
13945
14112
|
if (deParallelRouterEligible && workerCount > 1) {
|
|
13946
14113
|
deParallelRouter = "routed";
|
|
@@ -14606,6 +14773,7 @@ async function executeRenderPipeline(input) {
|
|
|
14606
14773
|
vp9CpuUsed: cfg.vp9CpuUsed,
|
|
14607
14774
|
useGpu: job.config.useGpu,
|
|
14608
14775
|
imageFormat: captureOptions.format || "jpeg",
|
|
14776
|
+
...videoLayer ? { videoLayer: videoLayer.layer } : {},
|
|
14609
14777
|
hdr: preset.hdr,
|
|
14610
14778
|
// HLS only: force an IDR every `gopSize` frames so the
|
|
14611
14779
|
// assemble stage's `-c copy` segmentation cuts exactly on
|
|
@@ -14936,12 +15104,12 @@ async function executeRenderPipeline(input) {
|
|
|
14936
15104
|
})
|
|
14937
15105
|
);
|
|
14938
15106
|
const totalElapsed = Date.now() - pipelineStart;
|
|
14939
|
-
const tmpPeakBytes =
|
|
15107
|
+
const tmpPeakBytes = existsSync17(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
14940
15108
|
recordTransientRetryObservability();
|
|
14941
15109
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
14942
15110
|
const observabilitySummary = observability.summary({
|
|
14943
15111
|
lastBrowserConsole,
|
|
14944
|
-
capture: captureObservability,
|
|
15112
|
+
capture: withCapturedBeyondViewport(captureObservability, dedupPerfs),
|
|
14945
15113
|
initFallback: mergeWorkerInitObservability(dedupPerfs),
|
|
14946
15114
|
extraction: extractionObservability,
|
|
14947
15115
|
compositionHash
|
|
@@ -15017,7 +15185,7 @@ async function executeRenderPipeline(input) {
|
|
|
15017
15185
|
}
|
|
15018
15186
|
}
|
|
15019
15187
|
if (job.config.debug) {
|
|
15020
|
-
if (!isPngSequence && !isHls &&
|
|
15188
|
+
if (!isPngSequence && !isHls && existsSync17(stagedOutputPath)) {
|
|
15021
15189
|
const debugOutput = join27(workDir, `output${videoExt}`);
|
|
15022
15190
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15023
15191
|
}
|
|
@@ -15077,7 +15245,7 @@ async function executeRenderPipeline(input) {
|
|
|
15077
15245
|
const failedStage = job.currentStage || "pipeline";
|
|
15078
15246
|
const observabilitySummary = observability.summary({
|
|
15079
15247
|
lastBrowserConsole,
|
|
15080
|
-
capture: captureObservability,
|
|
15248
|
+
capture: withCapturedBeyondViewport(captureObservability, dedupPerfs),
|
|
15081
15249
|
initFallback: mergeWorkerInitObservability(dedupPerfs),
|
|
15082
15250
|
extraction: extractionObservability,
|
|
15083
15251
|
compositionHash
|
|
@@ -15123,7 +15291,7 @@ async function executeRenderPipeline(input) {
|
|
|
15123
15291
|
}
|
|
15124
15292
|
|
|
15125
15293
|
// src/services/distributed/renderSpan.ts
|
|
15126
|
-
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as
|
|
15294
|
+
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync13, rmSync as rmSync16 } from "node:fs";
|
|
15127
15295
|
import { join as join40 } from "node:path";
|
|
15128
15296
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15129
15297
|
|
|
@@ -15515,7 +15683,7 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
15515
15683
|
import {
|
|
15516
15684
|
copyFileSync as copyFileSync6,
|
|
15517
15685
|
cpSync as cpSync2,
|
|
15518
|
-
existsSync as
|
|
15686
|
+
existsSync as existsSync22,
|
|
15519
15687
|
linkSync,
|
|
15520
15688
|
mkdirSync as mkdirSync14,
|
|
15521
15689
|
rmSync as rmSync12,
|
|
@@ -15546,7 +15714,7 @@ import {
|
|
|
15546
15714
|
} from "@hyperframes/engine";
|
|
15547
15715
|
|
|
15548
15716
|
// src/services/distributed/chunkFrames.ts
|
|
15549
|
-
import { existsSync as
|
|
15717
|
+
import { existsSync as existsSync19, readdirSync as readdirSync6 } from "node:fs";
|
|
15550
15718
|
import { extname as extname4, join as join30 } from "node:path";
|
|
15551
15719
|
import {
|
|
15552
15720
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -15558,7 +15726,7 @@ import {
|
|
|
15558
15726
|
// src/services/distributed/shared.ts
|
|
15559
15727
|
import { execFile as execFileCallback } from "node:child_process";
|
|
15560
15728
|
import { dirname as dirname13, join as join29 } from "node:path";
|
|
15561
|
-
import { existsSync as
|
|
15729
|
+
import { existsSync as existsSync18, readFileSync as readFileSync8 } from "node:fs";
|
|
15562
15730
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15563
15731
|
import { promisify as promisify2 } from "node:util";
|
|
15564
15732
|
import {
|
|
@@ -15574,7 +15742,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
15574
15742
|
function resolvePlanAudioPath(planDir) {
|
|
15575
15743
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
15576
15744
|
const candidate = join29(planDir, name);
|
|
15577
|
-
if (
|
|
15745
|
+
if (existsSync18(candidate)) return candidate;
|
|
15578
15746
|
}
|
|
15579
15747
|
return null;
|
|
15580
15748
|
}
|
|
@@ -15835,9 +16003,9 @@ function readProducerVersion() {
|
|
|
15835
16003
|
let current = startDir;
|
|
15836
16004
|
for (let i = 0; i < 10; i++) {
|
|
15837
16005
|
const candidate = join29(current, "package.json");
|
|
15838
|
-
if (
|
|
16006
|
+
if (existsSync18(candidate)) {
|
|
15839
16007
|
try {
|
|
15840
|
-
const pkg = JSON.parse(
|
|
16008
|
+
const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
|
|
15841
16009
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
15842
16010
|
cachedProducerVersion = pkg.version;
|
|
15843
16011
|
return pkg.version;
|
|
@@ -15889,7 +16057,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
15889
16057
|
const result = [];
|
|
15890
16058
|
for (const v of videos) {
|
|
15891
16059
|
const outputDir = join30(planDir, "video-frames", v.videoId);
|
|
15892
|
-
if (!
|
|
16060
|
+
if (!existsSync19(outputDir)) {
|
|
15893
16061
|
throw new Error(
|
|
15894
16062
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
15895
16063
|
);
|
|
@@ -16005,7 +16173,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16005
16173
|
const range = video.deferredRange;
|
|
16006
16174
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16007
16175
|
const sourcePath = remoteUrl ?? join30(input.planDir, ...range.sourcePath.split("/"));
|
|
16008
|
-
if (remoteUrl === void 0 && !
|
|
16176
|
+
if (remoteUrl === void 0 && !existsSync19(sourcePath)) {
|
|
16009
16177
|
throw new RenderChunkValidationError(
|
|
16010
16178
|
MISSING_PLAN_ARTIFACT,
|
|
16011
16179
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -16167,7 +16335,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
16167
16335
|
}
|
|
16168
16336
|
|
|
16169
16337
|
// src/services/render/stages/freezePlan.ts
|
|
16170
|
-
import { existsSync as
|
|
16338
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
16171
16339
|
import { join as join32, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16172
16340
|
|
|
16173
16341
|
// src/services/distributed/planProtocol.ts
|
|
@@ -16361,7 +16529,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
16361
16529
|
return sha256;
|
|
16362
16530
|
}
|
|
16363
16531
|
async function prehashPlanFiles(dir, digests) {
|
|
16364
|
-
if (!
|
|
16532
|
+
if (!existsSync20(dir)) return;
|
|
16365
16533
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
16366
16534
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
16367
16535
|
}
|
|
@@ -16407,7 +16575,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
16407
16575
|
const assets = [];
|
|
16408
16576
|
for (const file of files) {
|
|
16409
16577
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
16410
|
-
compositionHtml =
|
|
16578
|
+
compositionHtml = readFileSync9(file.absolutePath);
|
|
16411
16579
|
continue;
|
|
16412
16580
|
}
|
|
16413
16581
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -16431,19 +16599,19 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
16431
16599
|
}
|
|
16432
16600
|
function readPlanRemoteFiles(planDir) {
|
|
16433
16601
|
const path = join32(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16434
|
-
return
|
|
16602
|
+
return existsSync20(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
|
|
16435
16603
|
}
|
|
16436
16604
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16437
16605
|
const planJsonPath = join32(planDir, "plan.json");
|
|
16438
16606
|
const encoderJsonPath = join32(planDir, "meta", "encoder.json");
|
|
16439
|
-
if (!
|
|
16607
|
+
if (!existsSync20(planJsonPath)) {
|
|
16440
16608
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
16441
16609
|
}
|
|
16442
|
-
if (!
|
|
16610
|
+
if (!existsSync20(encoderJsonPath)) {
|
|
16443
16611
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
16444
16612
|
}
|
|
16445
|
-
const planJson = JSON.parse(
|
|
16446
|
-
const encoderConfigCanonicalJson =
|
|
16613
|
+
const planJson = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
|
|
16614
|
+
const encoderConfigCanonicalJson = readFileSync9(encoderJsonPath, "utf-8");
|
|
16447
16615
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
16448
16616
|
return computePlanHash({
|
|
16449
16617
|
compositionHtml,
|
|
@@ -16469,11 +16637,11 @@ async function freezePlan(input) {
|
|
|
16469
16637
|
hasAudio,
|
|
16470
16638
|
digests
|
|
16471
16639
|
} = input;
|
|
16472
|
-
if (!
|
|
16640
|
+
if (!existsSync20(planDir)) {
|
|
16473
16641
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
16474
16642
|
}
|
|
16475
16643
|
const metaDir = join32(planDir, "meta");
|
|
16476
|
-
if (!
|
|
16644
|
+
if (!existsSync20(metaDir)) mkdirSync12(metaDir, { recursive: true });
|
|
16477
16645
|
writeFileSync7(
|
|
16478
16646
|
join32(metaDir, "composition.json"),
|
|
16479
16647
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -16549,7 +16717,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
16549
16717
|
}
|
|
16550
16718
|
|
|
16551
16719
|
// src/services/distributed/chunkAudio.ts
|
|
16552
|
-
import { closeSync as closeSync6, existsSync as
|
|
16720
|
+
import { closeSync as closeSync6, existsSync as existsSync21, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync10 } from "node:fs";
|
|
16553
16721
|
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
16554
16722
|
import { join as join33 } from "node:path";
|
|
16555
16723
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -16637,7 +16805,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
16637
16805
|
offset += track.sizes[offsets.length - 1];
|
|
16638
16806
|
}
|
|
16639
16807
|
});
|
|
16640
|
-
const bytes =
|
|
16808
|
+
const bytes = readFileSync10(input.audioPath);
|
|
16641
16809
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
16642
16810
|
mkdirSync13(join33(input.planDir, SLICE_DIR), { recursive: true });
|
|
16643
16811
|
input.chunks.forEach((_, index) => {
|
|
@@ -16655,9 +16823,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
16655
16823
|
}
|
|
16656
16824
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
16657
16825
|
const layoutPath = join33(planDir, SLICE_LAYOUT);
|
|
16658
|
-
if (!
|
|
16659
|
-
const layout = JSON.parse(
|
|
16660
|
-
const frame =
|
|
16826
|
+
if (!existsSync21(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
16827
|
+
const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
|
|
16828
|
+
const frame = readFileSync10(join33(planDir, SLICE_FRAME));
|
|
16661
16829
|
const fd = openSync6(scratchPath, "w");
|
|
16662
16830
|
try {
|
|
16663
16831
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -16670,7 +16838,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
16670
16838
|
);
|
|
16671
16839
|
const start = layout.partStarts[chunkIndex];
|
|
16672
16840
|
if (start !== void 0) {
|
|
16673
|
-
const part =
|
|
16841
|
+
const part = readFileSync10(join33(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
16674
16842
|
writeSync(fd, part, 0, part.length, start);
|
|
16675
16843
|
}
|
|
16676
16844
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -17215,7 +17383,7 @@ function createVideoSourceShipper(input) {
|
|
|
17215
17383
|
let result = null;
|
|
17216
17384
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
17217
17385
|
const rel = pathInside(root, srcPath);
|
|
17218
|
-
if (rel !== null && (
|
|
17386
|
+
if (rel !== null && (existsSync22(join35(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
17219
17387
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
17220
17388
|
break;
|
|
17221
17389
|
}
|
|
@@ -17243,7 +17411,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17243
17411
|
useGpu: false,
|
|
17244
17412
|
browserGpuMode: "software"
|
|
17245
17413
|
});
|
|
17246
|
-
if (!
|
|
17414
|
+
if (!existsSync22(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
17247
17415
|
const log = config.logger ?? defaultLogger;
|
|
17248
17416
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
17249
17417
|
const abortSignal = config.abortSignal;
|
|
@@ -17273,7 +17441,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17273
17441
|
});
|
|
17274
17442
|
const entryFile = config.entryFile ?? "index.html";
|
|
17275
17443
|
const htmlPath = join35(projectDir, entryFile);
|
|
17276
|
-
if (!
|
|
17444
|
+
if (!existsSync22(htmlPath)) {
|
|
17277
17445
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
17278
17446
|
}
|
|
17279
17447
|
const workDir = join35(planDir, ".plan-work");
|
|
@@ -17484,15 +17652,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17484
17652
|
logPlanStage(log, "extract", stageStarted);
|
|
17485
17653
|
const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
|
|
17486
17654
|
const videoFramesDst = join35(planDir, "video-frames");
|
|
17487
|
-
if (
|
|
17655
|
+
if (existsSync22(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
|
|
17488
17656
|
const videoSourcesDir = join35(planDir, "video-sources");
|
|
17489
17657
|
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
17490
|
-
if (
|
|
17658
|
+
if (existsSync22(stagedVideoFrames)) {
|
|
17491
17659
|
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
17492
17660
|
} else {
|
|
17493
17661
|
mkdirSync14(videoFramesDst, { recursive: true });
|
|
17494
17662
|
}
|
|
17495
|
-
if (
|
|
17663
|
+
if (existsSync22(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
17496
17664
|
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
17497
17665
|
const shipVideoSource = createVideoSourceShipper({
|
|
17498
17666
|
planDir,
|
|
@@ -17612,10 +17780,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17612
17780
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
17613
17781
|
}
|
|
17614
17782
|
const planAudioPath = join35(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
17615
|
-
if (audioResult?.hasAudio &&
|
|
17783
|
+
if (audioResult?.hasAudio && existsSync22(audioResult.audioOutputPath)) {
|
|
17616
17784
|
renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
|
|
17617
17785
|
}
|
|
17618
|
-
if (chunksCarryAudio &&
|
|
17786
|
+
if (chunksCarryAudio && existsSync22(planAudioPath)) {
|
|
17619
17787
|
if (options.sliceChunkAudio) {
|
|
17620
17788
|
stageStarted = Date.now();
|
|
17621
17789
|
await writeChunkAudioSlices({
|
|
@@ -17730,7 +17898,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
17730
17898
|
|
|
17731
17899
|
// src/services/distributed/renderChunk.ts
|
|
17732
17900
|
import { randomBytes } from "node:crypto";
|
|
17733
|
-
import { existsSync as
|
|
17901
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync17, readFileSync as readFileSync12, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
|
|
17734
17902
|
import { cpus as cpus3 } from "node:os";
|
|
17735
17903
|
import { join as join39 } from "node:path";
|
|
17736
17904
|
import {
|
|
@@ -17753,11 +17921,11 @@ import {
|
|
|
17753
17921
|
// src/services/distributed/planV2.ts
|
|
17754
17922
|
import {
|
|
17755
17923
|
copyFileSync as copyFileSync8,
|
|
17756
|
-
existsSync as
|
|
17924
|
+
existsSync as existsSync24,
|
|
17757
17925
|
lstatSync as lstatSync3,
|
|
17758
17926
|
mkdirSync as mkdirSync16,
|
|
17759
17927
|
mkdtempSync as mkdtempSync6,
|
|
17760
|
-
readFileSync as
|
|
17928
|
+
readFileSync as readFileSync11,
|
|
17761
17929
|
readdirSync as readdirSync9,
|
|
17762
17930
|
rmSync as rmSync14,
|
|
17763
17931
|
statSync as statSync10,
|
|
@@ -17771,7 +17939,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
17771
17939
|
// src/services/distributed/planV2Publisher.ts
|
|
17772
17940
|
import {
|
|
17773
17941
|
copyFileSync as copyFileSync7,
|
|
17774
|
-
existsSync as
|
|
17942
|
+
existsSync as existsSync23,
|
|
17775
17943
|
linkSync as linkSync2,
|
|
17776
17944
|
mkdirSync as mkdirSync15,
|
|
17777
17945
|
mkdtempSync as mkdtempSync5,
|
|
@@ -17840,7 +18008,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17840
18008
|
#linkFile;
|
|
17841
18009
|
#committed = false;
|
|
17842
18010
|
constructor(destinationDir, options = {}) {
|
|
17843
|
-
if (
|
|
18011
|
+
if (existsSync23(destinationDir)) {
|
|
17844
18012
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
17845
18013
|
}
|
|
17846
18014
|
this.destinationDir = destinationDir;
|
|
@@ -17857,7 +18025,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17857
18025
|
);
|
|
17858
18026
|
}
|
|
17859
18027
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
17860
|
-
if (
|
|
18028
|
+
if (existsSync23(destinationPath)) return;
|
|
17861
18029
|
mkdirSync15(dirname14(destinationPath), { recursive: true });
|
|
17862
18030
|
const stagingDir = mkdtempSync5(join37(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
17863
18031
|
const temporaryPath = join37(stagingDir, "blob");
|
|
@@ -17875,7 +18043,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
17875
18043
|
}
|
|
17876
18044
|
async commitManifest(manifestBytes) {
|
|
17877
18045
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
17878
|
-
if (!
|
|
18046
|
+
if (!existsSync23(planV2BlobPath(this.temporaryDir, digest))) {
|
|
17879
18047
|
throw new PlanV2IntegrityError(
|
|
17880
18048
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
17881
18049
|
);
|
|
@@ -17906,7 +18074,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
17906
18074
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
17907
18075
|
}
|
|
17908
18076
|
function readJsonFile(path, label) {
|
|
17909
|
-
const contents =
|
|
18077
|
+
const contents = readFileSync11(path, "utf-8");
|
|
17910
18078
|
try {
|
|
17911
18079
|
const parsed = JSON.parse(contents);
|
|
17912
18080
|
return parsed;
|
|
@@ -17954,7 +18122,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
17954
18122
|
}
|
|
17955
18123
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
17956
18124
|
const videoRoot = join38(executionPlanDir, "video-frames");
|
|
17957
|
-
if (!
|
|
18125
|
+
if (!existsSync24(videoRoot)) return;
|
|
17958
18126
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
17959
18127
|
if (!videoEntry.isDirectory()) continue;
|
|
17960
18128
|
const videoDir = join38(videoRoot, videoEntry.name);
|
|
@@ -18055,7 +18223,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18055
18223
|
}
|
|
18056
18224
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18057
18225
|
const videosPath = join38(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18058
|
-
if (!
|
|
18226
|
+
if (!existsSync24(videosPath)) return;
|
|
18059
18227
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18060
18228
|
for (const video of videos.extracted) {
|
|
18061
18229
|
mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -18089,9 +18257,9 @@ function parseChunkSlices(value) {
|
|
|
18089
18257
|
}
|
|
18090
18258
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18091
18259
|
const videoRoot = join38(executionPlanDir, "video-frames");
|
|
18092
|
-
const hasExtractedFrames =
|
|
18260
|
+
const hasExtractedFrames = existsSync24(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18093
18261
|
const videosPath = join38(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18094
|
-
if (!
|
|
18262
|
+
if (!existsSync24(videosPath)) {
|
|
18095
18263
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18096
18264
|
}
|
|
18097
18265
|
const chunksPath = join38(executionPlanDir, "meta", "chunks.json");
|
|
@@ -18148,7 +18316,7 @@ function computeManifestHash(manifest) {
|
|
|
18148
18316
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
18149
18317
|
}
|
|
18150
18318
|
function writeBlob(sourcePath, destinationPath) {
|
|
18151
|
-
if (
|
|
18319
|
+
if (existsSync24(destinationPath)) return;
|
|
18152
18320
|
mkdirSync16(dirname15(destinationPath), { recursive: true });
|
|
18153
18321
|
const temporaryDir = mkdtempSync6(join38(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18154
18322
|
const temporaryPath = join38(temporaryDir, "blob");
|
|
@@ -18188,7 +18356,7 @@ async function putTasks(tasks) {
|
|
|
18188
18356
|
}
|
|
18189
18357
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18190
18358
|
const executionPlanPath = join38(executionPlanDir, "plan.json");
|
|
18191
|
-
if (!
|
|
18359
|
+
if (!existsSync24(executionPlanPath)) {
|
|
18192
18360
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18193
18361
|
}
|
|
18194
18362
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -18268,7 +18436,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18268
18436
|
};
|
|
18269
18437
|
}
|
|
18270
18438
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
18271
|
-
if (
|
|
18439
|
+
if (existsSync24(planV2Dir)) {
|
|
18272
18440
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18273
18441
|
}
|
|
18274
18442
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -18360,7 +18528,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18360
18528
|
}
|
|
18361
18529
|
}
|
|
18362
18530
|
async function planV2(projectDir, config, planV2Dir) {
|
|
18363
|
-
if (
|
|
18531
|
+
if (existsSync24(planV2Dir)) {
|
|
18364
18532
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18365
18533
|
}
|
|
18366
18534
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -18509,7 +18677,7 @@ function parsePlanV2Manifest(value) {
|
|
|
18509
18677
|
}
|
|
18510
18678
|
function readPlanV2Manifest(planV2Dir) {
|
|
18511
18679
|
const manifestPath = join38(planV2Dir, "plan.json");
|
|
18512
|
-
if (!
|
|
18680
|
+
if (!existsSync24(manifestPath)) {
|
|
18513
18681
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18514
18682
|
}
|
|
18515
18683
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -18528,7 +18696,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
18528
18696
|
}
|
|
18529
18697
|
function verifyBlob(planV2Dir, artifact) {
|
|
18530
18698
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
18531
|
-
if (!
|
|
18699
|
+
if (!existsSync24(sourcePath)) {
|
|
18532
18700
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18533
18701
|
}
|
|
18534
18702
|
const stats = statSync10(sourcePath);
|
|
@@ -18541,7 +18709,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18541
18709
|
return sourcePath;
|
|
18542
18710
|
}
|
|
18543
18711
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
18544
|
-
if (
|
|
18712
|
+
if (existsSync24(destinationDir)) {
|
|
18545
18713
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
18546
18714
|
}
|
|
18547
18715
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -18606,7 +18774,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
18606
18774
|
}
|
|
18607
18775
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
18608
18776
|
const markerPath = join38(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
18609
|
-
if (!
|
|
18777
|
+
if (!existsSync24(markerPath)) return null;
|
|
18610
18778
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
18611
18779
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
18612
18780
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -18624,7 +18792,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18624
18792
|
for (const artifact of required) {
|
|
18625
18793
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
18626
18794
|
const path = join38(planDir, ...artifact.path.split("/"));
|
|
18627
|
-
if (!
|
|
18795
|
+
if (!existsSync24(path) || statSync10(path).size !== artifact.sizeBytes) {
|
|
18628
18796
|
throw new PlanV2IntegrityError(
|
|
18629
18797
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
18630
18798
|
);
|
|
@@ -18741,10 +18909,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
18741
18909
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
18742
18910
|
}
|
|
18743
18911
|
function hashChunkOutput(outputPath, kind) {
|
|
18744
|
-
if (kind === "file") return sha256Hex(
|
|
18912
|
+
if (kind === "file") return sha256Hex(readFileSync12(outputPath));
|
|
18745
18913
|
const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
18746
18914
|
const lines = entries.map(
|
|
18747
|
-
(name) => `${name}\0${sha256Hex(
|
|
18915
|
+
(name) => `${name}\0${sha256Hex(readFileSync12(join39(outputPath, name)))}`
|
|
18748
18916
|
);
|
|
18749
18917
|
return sha256Hex(lines.join("\0"));
|
|
18750
18918
|
}
|
|
@@ -18764,13 +18932,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18764
18932
|
const planJsonPath = join39(planDir, "plan.json");
|
|
18765
18933
|
const encoderJsonPath = join39(planDir, "meta", "encoder.json");
|
|
18766
18934
|
const chunksJsonPath = join39(planDir, "meta", "chunks.json");
|
|
18767
|
-
if (!
|
|
18935
|
+
if (!existsSync25(planJsonPath)) {
|
|
18768
18936
|
throw new RenderChunkValidationError(
|
|
18769
18937
|
MISSING_PLAN_ARTIFACT,
|
|
18770
18938
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
18771
18939
|
);
|
|
18772
18940
|
}
|
|
18773
|
-
const plan2 = JSON.parse(
|
|
18941
|
+
const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
|
|
18774
18942
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
18775
18943
|
const planHashStarted = Date.now();
|
|
18776
18944
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -18780,20 +18948,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18780
18948
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
18781
18949
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
18782
18950
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
18783
|
-
if (!
|
|
18951
|
+
if (!existsSync25(required)) {
|
|
18784
18952
|
throw new RenderChunkValidationError(
|
|
18785
18953
|
MISSING_PLAN_ARTIFACT,
|
|
18786
18954
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
18787
18955
|
);
|
|
18788
18956
|
}
|
|
18789
18957
|
}
|
|
18790
|
-
const encoder = JSON.parse(
|
|
18791
|
-
const chunks = JSON.parse(
|
|
18958
|
+
const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
|
|
18959
|
+
const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
|
|
18792
18960
|
const videosJsonPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18793
18961
|
let planVideos = null;
|
|
18794
|
-
if (
|
|
18962
|
+
if (existsSync25(videosJsonPath)) {
|
|
18795
18963
|
try {
|
|
18796
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
18964
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
|
|
18797
18965
|
} catch (err) {
|
|
18798
18966
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
18799
18967
|
throw new RenderChunkValidationError(
|
|
@@ -18822,7 +18990,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18822
18990
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
18823
18991
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
18824
18992
|
const compiledDir = join39(planDir, "compiled");
|
|
18825
|
-
if (!
|
|
18993
|
+
if (!existsSync25(compiledDir)) {
|
|
18826
18994
|
throw new RenderChunkValidationError(
|
|
18827
18995
|
MISSING_PLAN_ARTIFACT,
|
|
18828
18996
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -18904,7 +19072,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18904
19072
|
dimensions: plan2.dimensions,
|
|
18905
19073
|
encoder,
|
|
18906
19074
|
videoCount: planVideos?.videos.length ?? 0,
|
|
18907
|
-
html:
|
|
19075
|
+
html: readFileSync12(join39(compiledDir, "index.html"), "utf-8"),
|
|
18908
19076
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
18909
19077
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
18910
19078
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19072,10 +19240,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19072
19240
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19073
19241
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19074
19242
|
if (isPngSequence) {
|
|
19075
|
-
if (!
|
|
19243
|
+
if (!existsSync25(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
19076
19244
|
} else {
|
|
19077
19245
|
const outDir = join39(outputChunkPath, "..");
|
|
19078
|
-
if (!
|
|
19246
|
+
if (!existsSync25(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
19079
19247
|
}
|
|
19080
19248
|
const encodeInput = {
|
|
19081
19249
|
job,
|
|
@@ -19366,7 +19534,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19366
19534
|
planned.totalFrames
|
|
19367
19535
|
);
|
|
19368
19536
|
const slices = JSON.parse(
|
|
19369
|
-
|
|
19537
|
+
readFileSync13(join40(planDir, "meta", "chunks.json"), "utf-8")
|
|
19370
19538
|
);
|
|
19371
19539
|
const parts = [];
|
|
19372
19540
|
for (const slice of slices) {
|
|
@@ -19969,7 +20137,7 @@ import {
|
|
|
19969
20137
|
|
|
19970
20138
|
// src/server.ts
|
|
19971
20139
|
import {
|
|
19972
|
-
existsSync as
|
|
20140
|
+
existsSync as existsSync28,
|
|
19973
20141
|
mkdirSync as mkdirSync19,
|
|
19974
20142
|
statSync as statSync12,
|
|
19975
20143
|
mkdtempSync as mkdtempSync8,
|
|
@@ -19988,8 +20156,8 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
19988
20156
|
|
|
19989
20157
|
// src/services/hyperframeLint.ts
|
|
19990
20158
|
import {
|
|
19991
|
-
existsSync as
|
|
19992
|
-
readFileSync as
|
|
20159
|
+
existsSync as existsSync26,
|
|
20160
|
+
readFileSync as readFileSync14,
|
|
19993
20161
|
statSync as statSync11,
|
|
19994
20162
|
openSync as openSync7,
|
|
19995
20163
|
fstatSync as fstatSync3,
|
|
@@ -20035,14 +20203,14 @@ function readEntryHtml(filePath) {
|
|
|
20035
20203
|
}
|
|
20036
20204
|
try {
|
|
20037
20205
|
if (!fstatSync3(fd).isFile()) return null;
|
|
20038
|
-
return
|
|
20206
|
+
return readFileSync14(fd, "utf-8");
|
|
20039
20207
|
} finally {
|
|
20040
20208
|
closeSync7(fd);
|
|
20041
20209
|
}
|
|
20042
20210
|
}
|
|
20043
20211
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20044
20212
|
const absProjectDir = resolve11(projectDir);
|
|
20045
|
-
if (!
|
|
20213
|
+
if (!existsSync26(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
20046
20214
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20047
20215
|
}
|
|
20048
20216
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20111,7 +20279,7 @@ async function runHyperframeLint(prepared) {
|
|
|
20111
20279
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
20112
20280
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
20113
20281
|
import { dirname as dirname16, join as join42 } from "node:path";
|
|
20114
|
-
import { existsSync as
|
|
20282
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
20115
20283
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
20116
20284
|
async function startHealthWorker(options = {}) {
|
|
20117
20285
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -20188,7 +20356,7 @@ function resolveWorkerEntry2() {
|
|
|
20188
20356
|
const here = dirname16(fileURLToPath6(import.meta.url));
|
|
20189
20357
|
const candidates = [join42(here, "healthWorkerThread.js"), join42(here, "healthWorkerThread.ts")];
|
|
20190
20358
|
for (const candidate of candidates) {
|
|
20191
|
-
if (
|
|
20359
|
+
if (existsSync27(candidate)) return candidate;
|
|
20192
20360
|
}
|
|
20193
20361
|
return void 0;
|
|
20194
20362
|
}
|
|
@@ -20331,7 +20499,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
20331
20499
|
const { input, cleanupProjectDir } = prepared;
|
|
20332
20500
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
20333
20501
|
const outputDir = dirname17(absoluteOutputPath);
|
|
20334
|
-
if (!
|
|
20502
|
+
if (!existsSync28(outputDir)) mkdirSync19(outputDir, { recursive: true });
|
|
20335
20503
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
20336
20504
|
}
|
|
20337
20505
|
function validateRenderOverrides(body) {
|
|
@@ -20372,11 +20540,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
20372
20540
|
const candidate = nonEmptyString(projectDir);
|
|
20373
20541
|
if (!candidate) return null;
|
|
20374
20542
|
const absProjectDir = resolve12(candidate);
|
|
20375
|
-
if (!
|
|
20543
|
+
if (!existsSync28(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20376
20544
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20377
20545
|
}
|
|
20378
20546
|
const entry = options.entryFile || "index.html";
|
|
20379
|
-
if (!
|
|
20547
|
+
if (!existsSync28(resolve12(absProjectDir, entry))) {
|
|
20380
20548
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
20381
20549
|
}
|
|
20382
20550
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -20468,7 +20636,7 @@ function cleanupTempDir(dir, log) {
|
|
|
20468
20636
|
}
|
|
20469
20637
|
}
|
|
20470
20638
|
function outputFileSize(path) {
|
|
20471
|
-
return
|
|
20639
|
+
return existsSync28(path) ? statSync12(path).size : 0;
|
|
20472
20640
|
}
|
|
20473
20641
|
function createBlockingProgressReporter(log, requestId) {
|
|
20474
20642
|
let lastLoggedPct = -10;
|
|
@@ -20770,7 +20938,7 @@ function createRenderHandlers(options = {}) {
|
|
|
20770
20938
|
if (!artifact) {
|
|
20771
20939
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
20772
20940
|
}
|
|
20773
|
-
if (!
|
|
20941
|
+
if (!existsSync28(artifact.path)) {
|
|
20774
20942
|
store.delete(token);
|
|
20775
20943
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
20776
20944
|
}
|
|
@@ -20872,10 +21040,10 @@ import { join as join45 } from "node:path";
|
|
|
20872
21040
|
// src/services/distributed/assemble.ts
|
|
20873
21041
|
import {
|
|
20874
21042
|
cpSync as cpSync3,
|
|
20875
|
-
existsSync as
|
|
21043
|
+
existsSync as existsSync29,
|
|
20876
21044
|
mkdirSync as mkdirSync20,
|
|
20877
21045
|
mkdtempSync as mkdtempSync9,
|
|
20878
|
-
readFileSync as
|
|
21046
|
+
readFileSync as readFileSync15,
|
|
20879
21047
|
readdirSync as readdirSync11,
|
|
20880
21048
|
rmSync as rmSync18,
|
|
20881
21049
|
statSync as statSync13,
|
|
@@ -20923,7 +21091,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
20923
21091
|
}
|
|
20924
21092
|
const format = plan2.dimensions.format;
|
|
20925
21093
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
20926
|
-
if (!
|
|
21094
|
+
if (!existsSync29(dirname18(outputPath))) {
|
|
20927
21095
|
mkdirSync20(dirname18(outputPath), { recursive: true });
|
|
20928
21096
|
}
|
|
20929
21097
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
@@ -20947,7 +21115,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
20947
21115
|
try {
|
|
20948
21116
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
20949
21117
|
const finalPath = join44(workDir, `output.${format}`);
|
|
20950
|
-
const audio = audioPath !== null &&
|
|
21118
|
+
const audio = audioPath !== null && existsSync29(audioPath) ? audioPath : null;
|
|
20951
21119
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
20952
21120
|
renameWithRetry10(finalPath, outputPath);
|
|
20953
21121
|
log.info("[assemble] stage timings", {
|
|
@@ -20965,7 +21133,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
20965
21133
|
});
|
|
20966
21134
|
}
|
|
20967
21135
|
}
|
|
20968
|
-
const fileSize =
|
|
21136
|
+
const fileSize = existsSync29(outputPath) ? statSync13(outputPath).size : 0;
|
|
20969
21137
|
return {
|
|
20970
21138
|
outputPath,
|
|
20971
21139
|
durationMs: Date.now() - start,
|
|
@@ -20976,16 +21144,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
20976
21144
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
20977
21145
|
const planJsonPath = join44(planDir, "plan.json");
|
|
20978
21146
|
const chunksJsonPath = join44(planDir, "meta", "chunks.json");
|
|
20979
|
-
if (!
|
|
21147
|
+
if (!existsSync29(planJsonPath)) {
|
|
20980
21148
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
20981
21149
|
}
|
|
20982
|
-
const plan2 = JSON.parse(
|
|
21150
|
+
const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
|
|
20983
21151
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
20984
21152
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
20985
|
-
if (!
|
|
21153
|
+
if (!existsSync29(chunksJsonPath)) {
|
|
20986
21154
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
20987
21155
|
}
|
|
20988
|
-
const chunks = JSON.parse(
|
|
21156
|
+
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
20989
21157
|
if (chunkPaths.length !== chunks.length) {
|
|
20990
21158
|
throw new Error(
|
|
20991
21159
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -20997,7 +21165,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
20997
21165
|
}
|
|
20998
21166
|
}
|
|
20999
21167
|
for (const path of chunkPaths.flat()) {
|
|
21000
|
-
if (!
|
|
21168
|
+
if (!existsSync29(path)) {
|
|
21001
21169
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
21002
21170
|
}
|
|
21003
21171
|
}
|
|
@@ -21131,13 +21299,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
21131
21299
|
return lockedAudio(planDir).separateAudio === true;
|
|
21132
21300
|
}
|
|
21133
21301
|
function planFormat(planDir) {
|
|
21134
|
-
const plan2 = JSON.parse(
|
|
21302
|
+
const plan2 = JSON.parse(readFileSync15(join44(planDir, "plan.json"), "utf-8"));
|
|
21135
21303
|
return plan2.dimensions.format;
|
|
21136
21304
|
}
|
|
21137
21305
|
function lockedAudio(planDir) {
|
|
21138
21306
|
const encoderJsonPath = join44(planDir, "meta", "encoder.json");
|
|
21139
|
-
if (!
|
|
21140
|
-
return JSON.parse(
|
|
21307
|
+
if (!existsSync29(encoderJsonPath)) return {};
|
|
21308
|
+
return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
21141
21309
|
}
|
|
21142
21310
|
function readCfrEncoder(planDir, format) {
|
|
21143
21311
|
if (format !== "mp4") {
|
|
@@ -21146,10 +21314,10 @@ function readCfrEncoder(planDir, format) {
|
|
|
21146
21314
|
);
|
|
21147
21315
|
}
|
|
21148
21316
|
const encoderJsonPath = join44(planDir, "meta", "encoder.json");
|
|
21149
|
-
if (!
|
|
21317
|
+
if (!existsSync29(encoderJsonPath)) {
|
|
21150
21318
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21151
21319
|
}
|
|
21152
|
-
const encoderJson = JSON.parse(
|
|
21320
|
+
const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
21153
21321
|
if (encoderJson.encoder === "libx265-software") {
|
|
21154
21322
|
throw new Error(
|
|
21155
21323
|
`[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
|
|
@@ -21158,7 +21326,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21158
21326
|
return encoderJson;
|
|
21159
21327
|
}
|
|
21160
21328
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
21161
|
-
if (
|
|
21329
|
+
if (existsSync29(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
21162
21330
|
mkdirSync20(outputPath, { recursive: true });
|
|
21163
21331
|
let globalIdx = 0;
|
|
21164
21332
|
for (const chunkDir of chunkPaths) {
|
|
@@ -21182,7 +21350,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21182
21350
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
21183
21351
|
);
|
|
21184
21352
|
}
|
|
21185
|
-
if (audioPath !== null &&
|
|
21353
|
+
if (audioPath !== null && existsSync29(audioPath)) {
|
|
21186
21354
|
const sidecar = join44(outputPath, MIXED_AUDIO_FILENAME4);
|
|
21187
21355
|
cpSync3(audioPath, sidecar);
|
|
21188
21356
|
}
|