@hyperframes/producer 0.9.13 → 0.9.15
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 +197 -132
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -62
- package/dist/index.js +275 -166
- package/dist/public-server.js +188 -82
- package/dist/services/render/audioPadTrim.d.ts +6 -0
- package/dist/services/render/stages/assembleStage.d.ts +1 -0
- package/dist/services/render/stages/audioStage.d.ts +4 -0
- package/dist/services/render/stages/captureStreamingStage.d.ts +4 -1
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,7 +8,7 @@ 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 existsSync17,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
@@ -71,7 +71,7 @@ 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 existsSync12,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
@@ -1920,7 +1920,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
1920
1920
|
// src/services/htmlCompiler.ts
|
|
1921
1921
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1922
1922
|
import { createHash as createHash4 } from "node:crypto";
|
|
1923
|
-
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1923
|
+
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1924
1924
|
import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1925
1925
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1926
1926
|
import {
|
|
@@ -3895,6 +3895,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
3895
3895
|
}
|
|
3896
3896
|
return result;
|
|
3897
3897
|
}
|
|
3898
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
3899
|
+
const families = [];
|
|
3900
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
3901
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
3902
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
3903
|
+
const sheet = resolve6(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
3904
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync6(sheet)) continue;
|
|
3905
|
+
for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
|
|
3906
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3907
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3908
|
+
const path = decodedUrlPath(url);
|
|
3909
|
+
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
|
|
3910
|
+
return isPathInside2(file, projectDir) && existsSync6(file);
|
|
3911
|
+
});
|
|
3912
|
+
if (family && shipsFile) families.push(family);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
return families;
|
|
3916
|
+
}
|
|
3898
3917
|
function collectFontFaceUrls(html) {
|
|
3899
3918
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
3900
3919
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -3925,12 +3944,10 @@ function collectFontFaceUrls(html) {
|
|
|
3925
3944
|
}
|
|
3926
3945
|
return urlSet;
|
|
3927
3946
|
}
|
|
3928
|
-
|
|
3929
|
-
const processed = await inlineExternalFontStylesheets(html);
|
|
3930
|
-
const urlSet = collectFontFaceUrls(processed);
|
|
3947
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
3931
3948
|
return downloadAndRewriteUrls(
|
|
3932
|
-
|
|
3933
|
-
|
|
3949
|
+
collectFontFaceUrls(html),
|
|
3950
|
+
html,
|
|
3934
3951
|
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3935
3952
|
"Remote font download failed",
|
|
3936
3953
|
"Localized remote font face(s)",
|
|
@@ -4098,15 +4115,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
4098
4115
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
4099
4116
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
4100
4117
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
4118
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
4119
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
4120
|
+
);
|
|
4101
4121
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
4102
|
-
injectTextRenderingRule(
|
|
4103
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
4104
|
-
)
|
|
4122
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
4105
4123
|
);
|
|
4106
4124
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
4107
4125
|
logger: defaultLogger,
|
|
4108
4126
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
4109
4127
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
4128
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
4110
4129
|
abortSignal: options.abortSignal
|
|
4111
4130
|
});
|
|
4112
4131
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -4132,7 +4151,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
4132
4151
|
);
|
|
4133
4152
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
4134
4153
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
4135
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
4154
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
4136
4155
|
const {
|
|
4137
4156
|
html: htmlWithJpegs,
|
|
4138
4157
|
preparedAssets: preparedHeifAssets,
|
|
@@ -5908,6 +5927,7 @@ import {
|
|
|
5908
5927
|
} from "@hyperframes/engine";
|
|
5909
5928
|
|
|
5910
5929
|
// src/services/render/stages/audioStage.ts
|
|
5930
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5911
5931
|
import { join as join12 } from "node:path";
|
|
5912
5932
|
import {
|
|
5913
5933
|
MIXED_AUDIO_FILENAME,
|
|
@@ -6041,8 +6061,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
6041
6061
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
6042
6062
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
6043
6063
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
6064
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
6044
6065
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
6045
|
-
return
|
|
6066
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
6046
6067
|
}
|
|
6047
6068
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
6048
6069
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -6080,7 +6101,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6080
6101
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
6081
6102
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
6082
6103
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
6083
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
6104
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
6084
6105
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
6085
6106
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
6086
6107
|
probeVideo(input.videoPath),
|
|
@@ -6128,7 +6149,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6128
6149
|
);
|
|
6129
6150
|
try {
|
|
6130
6151
|
for (const step of plan.steps) {
|
|
6131
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6152
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6153
|
+
input.sourcePath ?? input.audioPath,
|
|
6154
|
+
input.outputPath,
|
|
6155
|
+
targetDurationSeconds,
|
|
6156
|
+
step.filter
|
|
6157
|
+
);
|
|
6132
6158
|
if (!ffmpegResult.success) {
|
|
6133
6159
|
return {
|
|
6134
6160
|
success: false,
|
|
@@ -6157,6 +6183,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6157
6183
|
if (probeTruePeak) {
|
|
6158
6184
|
const correction = await enforceAacTruePeak({
|
|
6159
6185
|
audioPath: input.outputPath,
|
|
6186
|
+
sourcePath: input.sourcePath,
|
|
6160
6187
|
targetDurationSeconds,
|
|
6161
6188
|
signal: input.signal,
|
|
6162
6189
|
probeTruePeak,
|
|
@@ -6192,9 +6219,27 @@ async function constrainAacTruePeak(input) {
|
|
|
6192
6219
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
6193
6220
|
signal: input.signal,
|
|
6194
6221
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
6195
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
6222
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
6223
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
6224
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
6225
|
+
timeoutMs,
|
|
6226
|
+
input.signal
|
|
6227
|
+
))
|
|
6196
6228
|
});
|
|
6197
6229
|
}
|
|
6230
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
6231
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
6232
|
+
const cutPath = join10(scratchDir, "source.wav");
|
|
6233
|
+
try {
|
|
6234
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
6235
|
+
if (!result.success) throw new Error(result.error);
|
|
6236
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
6237
|
+
return true;
|
|
6238
|
+
} catch {
|
|
6239
|
+
removeScratch(input.sourcePath);
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6198
6243
|
async function enforceAacTruePeak(input) {
|
|
6199
6244
|
let scratchDir;
|
|
6200
6245
|
try {
|
|
@@ -6214,8 +6259,10 @@ async function enforceAacTruePeak(input) {
|
|
|
6214
6259
|
}
|
|
6215
6260
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
6216
6261
|
if (measuredPath !== correctedPath) return { success: true };
|
|
6262
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
6217
6263
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
6218
|
-
|
|
6264
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
6265
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
6219
6266
|
}
|
|
6220
6267
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
6221
6268
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -6225,7 +6272,7 @@ async function enforceAacTruePeak(input) {
|
|
|
6225
6272
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
6226
6273
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
6227
6274
|
const result = await input.encode(
|
|
6228
|
-
input.audioPath,
|
|
6275
|
+
input.sourcePath ?? input.audioPath,
|
|
6229
6276
|
correctedPath,
|
|
6230
6277
|
input.targetDurationSeconds,
|
|
6231
6278
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -6454,36 +6501,54 @@ async function runAudioStageScoped(input) {
|
|
|
6454
6501
|
const stageStart = Date.now();
|
|
6455
6502
|
const pcmSourcePath = join12(input.workDir, "audio-source.wav");
|
|
6456
6503
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6504
|
+
let deliverySourcePath;
|
|
6457
6505
|
try {
|
|
6458
6506
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
6459
6507
|
let audioLoweredDb;
|
|
6460
6508
|
if (result.hasAudio) {
|
|
6461
6509
|
const guardStart = Date.now();
|
|
6462
|
-
const
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
});
|
|
6470
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6471
|
-
const peak = await constrainAacTruePeak({
|
|
6472
|
-
audioPath: result.audioOutputPath,
|
|
6473
|
-
sourcePath: pcmSourcePath,
|
|
6474
|
-
targetDurationSeconds: duration,
|
|
6475
|
-
signal: input.abortSignal,
|
|
6476
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
6477
|
-
});
|
|
6478
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6479
|
-
audioLoweredDb = peak.loweredDb;
|
|
6510
|
+
const guard = await guardDeliveryPeaks(
|
|
6511
|
+
input,
|
|
6512
|
+
result.audioOutputPath,
|
|
6513
|
+
pcmSourcePath,
|
|
6514
|
+
duration
|
|
6515
|
+
);
|
|
6516
|
+
audioLoweredDb = guard.loweredDb;
|
|
6480
6517
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6518
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync9(pcmSourcePath)) {
|
|
6519
|
+
deliverySourcePath = pcmSourcePath;
|
|
6520
|
+
}
|
|
6481
6521
|
}
|
|
6482
|
-
return {
|
|
6522
|
+
return {
|
|
6523
|
+
...result,
|
|
6524
|
+
audioLoweredDb,
|
|
6525
|
+
deliverySourcePath,
|
|
6526
|
+
audioProcessMs: Date.now() - stageStart
|
|
6527
|
+
};
|
|
6483
6528
|
} finally {
|
|
6484
|
-
removeScratch2(pcmSourcePath);
|
|
6529
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
6485
6530
|
}
|
|
6486
6531
|
}
|
|
6532
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
6533
|
+
const limited = await limitDeliveryPeaks({
|
|
6534
|
+
audioPath,
|
|
6535
|
+
sourcePath,
|
|
6536
|
+
targetDurationSeconds: duration,
|
|
6537
|
+
signal: input.abortSignal,
|
|
6538
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
6539
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
6540
|
+
});
|
|
6541
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6542
|
+
const peak = await constrainAacTruePeak({
|
|
6543
|
+
audioPath,
|
|
6544
|
+
sourcePath,
|
|
6545
|
+
targetDurationSeconds: duration,
|
|
6546
|
+
signal: input.abortSignal,
|
|
6547
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
6548
|
+
});
|
|
6549
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6550
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
6551
|
+
}
|
|
6487
6552
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
6488
6553
|
const {
|
|
6489
6554
|
projectDir,
|
|
@@ -6578,7 +6643,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6578
6643
|
}
|
|
6579
6644
|
|
|
6580
6645
|
// src/services/render/stages/captureStage.ts
|
|
6581
|
-
import { existsSync as
|
|
6646
|
+
import { existsSync as existsSync10, readdirSync, statfsSync } from "node:fs";
|
|
6582
6647
|
import { join as join13 } from "node:path";
|
|
6583
6648
|
import {
|
|
6584
6649
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6630,7 +6695,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6630
6695
|
}
|
|
6631
6696
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6632
6697
|
let total = sampleDirectoryBytes(framesDir);
|
|
6633
|
-
if (!
|
|
6698
|
+
if (!existsSync10(workDir)) return total;
|
|
6634
6699
|
for (const attempt of readdirSync(workDir)) {
|
|
6635
6700
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6636
6701
|
total += sampleDirectoryBytes(join13(workDir, attempt));
|
|
@@ -6967,10 +7032,10 @@ import {
|
|
|
6967
7032
|
// src/services/render/stages/encodeStage.ts
|
|
6968
7033
|
import {
|
|
6969
7034
|
copyFileSync as copyFileSync4,
|
|
6970
|
-
existsSync as
|
|
7035
|
+
existsSync as existsSync11,
|
|
6971
7036
|
mkdirSync as mkdirSync5,
|
|
6972
7037
|
readdirSync as readdirSync2,
|
|
6973
|
-
readFileSync as
|
|
7038
|
+
readFileSync as readFileSync5,
|
|
6974
7039
|
rmSync as rmSync4,
|
|
6975
7040
|
statSync as statSync4,
|
|
6976
7041
|
writeFileSync as writeFileSync3
|
|
@@ -7064,7 +7129,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
7064
7129
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
7065
7130
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
7066
7131
|
}
|
|
7067
|
-
const gif =
|
|
7132
|
+
const gif = readFileSync5(outputPath);
|
|
7068
7133
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
7069
7134
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
7070
7135
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -7097,11 +7162,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7097
7162
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
7098
7163
|
}
|
|
7099
7164
|
}
|
|
7100
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
7165
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
|
|
7101
7166
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7102
7167
|
if (failure) return failure;
|
|
7103
7168
|
}
|
|
7104
|
-
const fileSize =
|
|
7169
|
+
const fileSize = existsSync11(outputPath) ? statSync4(outputPath).size : 0;
|
|
7105
7170
|
return {
|
|
7106
7171
|
success: true,
|
|
7107
7172
|
outputPath,
|
|
@@ -7262,7 +7327,7 @@ async function runEncodeStage(input) {
|
|
|
7262
7327
|
const stage5Start = Date.now();
|
|
7263
7328
|
if (isPngSequence) {
|
|
7264
7329
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7265
|
-
if (!
|
|
7330
|
+
if (!existsSync11(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7266
7331
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7267
7332
|
if (captured.length === 0) {
|
|
7268
7333
|
throw new Error(
|
|
@@ -7273,7 +7338,7 @@ async function runEncodeStage(input) {
|
|
|
7273
7338
|
const dst = join14(outputPath, formatExportFrameName(i, "png"));
|
|
7274
7339
|
copyFileSync4(join14(framesDir, name), dst);
|
|
7275
7340
|
});
|
|
7276
|
-
if (hasAudio && audioOutputPath &&
|
|
7341
|
+
if (hasAudio && audioOutputPath && existsSync11(audioOutputPath)) {
|
|
7277
7342
|
copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7278
7343
|
log.info(
|
|
7279
7344
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7440,7 +7505,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7440
7505
|
let rangeStart = null;
|
|
7441
7506
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7442
7507
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7443
|
-
const missing = !
|
|
7508
|
+
const missing = !existsSync12(framePath) || statSync5(framePath).size <= 8;
|
|
7444
7509
|
if (missing && rangeStart === null) {
|
|
7445
7510
|
rangeStart = frameIndex;
|
|
7446
7511
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7530,7 +7595,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7530
7595
|
let captured = 0;
|
|
7531
7596
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7532
7597
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7533
|
-
if (
|
|
7598
|
+
if (existsSync12(framePath)) captured++;
|
|
7534
7599
|
}
|
|
7535
7600
|
return captured;
|
|
7536
7601
|
}
|
|
@@ -7757,7 +7822,7 @@ import {
|
|
|
7757
7822
|
} from "@hyperframes/engine";
|
|
7758
7823
|
|
|
7759
7824
|
// src/services/distributed/chunkFrames.ts
|
|
7760
|
-
import { existsSync as
|
|
7825
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4 } from "node:fs";
|
|
7761
7826
|
import { extname as extname2, join as join17 } from "node:path";
|
|
7762
7827
|
import {
|
|
7763
7828
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7769,7 +7834,7 @@ import {
|
|
|
7769
7834
|
// src/services/distributed/shared.ts
|
|
7770
7835
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7771
7836
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7772
|
-
import { existsSync as
|
|
7837
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6 } from "node:fs";
|
|
7773
7838
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7774
7839
|
import { promisify as promisify2 } from "node:util";
|
|
7775
7840
|
import {
|
|
@@ -7785,7 +7850,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7785
7850
|
function resolvePlanAudioPath(planDir) {
|
|
7786
7851
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7787
7852
|
const candidate = join16(planDir, name);
|
|
7788
|
-
if (
|
|
7853
|
+
if (existsSync13(candidate)) return candidate;
|
|
7789
7854
|
}
|
|
7790
7855
|
return null;
|
|
7791
7856
|
}
|
|
@@ -8046,9 +8111,9 @@ function readProducerVersion() {
|
|
|
8046
8111
|
let current = startDir;
|
|
8047
8112
|
for (let i = 0; i < 10; i++) {
|
|
8048
8113
|
const candidate = join16(current, "package.json");
|
|
8049
|
-
if (
|
|
8114
|
+
if (existsSync13(candidate)) {
|
|
8050
8115
|
try {
|
|
8051
|
-
const pkg = JSON.parse(
|
|
8116
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8052
8117
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
8053
8118
|
cachedProducerVersion = pkg.version;
|
|
8054
8119
|
return pkg.version;
|
|
@@ -8100,7 +8165,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8100
8165
|
const result = [];
|
|
8101
8166
|
for (const v of videos) {
|
|
8102
8167
|
const outputDir = join17(planDir, "video-frames", v.videoId);
|
|
8103
|
-
if (!
|
|
8168
|
+
if (!existsSync14(outputDir)) {
|
|
8104
8169
|
throw new Error(
|
|
8105
8170
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8106
8171
|
);
|
|
@@ -8216,7 +8281,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8216
8281
|
const range = video.deferredRange;
|
|
8217
8282
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8218
8283
|
const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
|
|
8219
|
-
if (remoteUrl === void 0 && !
|
|
8284
|
+
if (remoteUrl === void 0 && !existsSync14(sourcePath)) {
|
|
8220
8285
|
throw new RenderChunkValidationError(
|
|
8221
8286
|
MISSING_PLAN_ARTIFACT,
|
|
8222
8287
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8434,7 +8499,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8434
8499
|
}
|
|
8435
8500
|
|
|
8436
8501
|
// src/services/render/stages/freezePlan.ts
|
|
8437
|
-
import { existsSync as
|
|
8502
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8438
8503
|
import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8439
8504
|
import {
|
|
8440
8505
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8628,7 +8693,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8628
8693
|
return sha256;
|
|
8629
8694
|
}
|
|
8630
8695
|
async function prehashPlanFiles(dir, digests) {
|
|
8631
|
-
if (!
|
|
8696
|
+
if (!existsSync15(dir)) return;
|
|
8632
8697
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8633
8698
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8634
8699
|
}
|
|
@@ -8674,7 +8739,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
8674
8739
|
const assets = [];
|
|
8675
8740
|
for (const file of files) {
|
|
8676
8741
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
8677
|
-
compositionHtml =
|
|
8742
|
+
compositionHtml = readFileSync7(file.absolutePath);
|
|
8678
8743
|
continue;
|
|
8679
8744
|
}
|
|
8680
8745
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -8698,19 +8763,19 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
8698
8763
|
}
|
|
8699
8764
|
function readPlanRemoteFiles(planDir) {
|
|
8700
8765
|
const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8701
|
-
return
|
|
8766
|
+
return existsSync15(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8702
8767
|
}
|
|
8703
8768
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8704
8769
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8705
8770
|
const encoderJsonPath = join20(planDir, "meta", "encoder.json");
|
|
8706
|
-
if (!
|
|
8771
|
+
if (!existsSync15(planJsonPath)) {
|
|
8707
8772
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8708
8773
|
}
|
|
8709
|
-
if (!
|
|
8774
|
+
if (!existsSync15(encoderJsonPath)) {
|
|
8710
8775
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8711
8776
|
}
|
|
8712
|
-
const planJson = JSON.parse(
|
|
8713
|
-
const encoderConfigCanonicalJson =
|
|
8777
|
+
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
8778
|
+
const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
|
|
8714
8779
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8715
8780
|
return computePlanHash({
|
|
8716
8781
|
compositionHtml,
|
|
@@ -8736,11 +8801,11 @@ async function freezePlan(input) {
|
|
|
8736
8801
|
hasAudio,
|
|
8737
8802
|
digests
|
|
8738
8803
|
} = input;
|
|
8739
|
-
if (!
|
|
8804
|
+
if (!existsSync15(planDir)) {
|
|
8740
8805
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8741
8806
|
}
|
|
8742
8807
|
const metaDir = join20(planDir, "meta");
|
|
8743
|
-
if (!
|
|
8808
|
+
if (!existsSync15(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8744
8809
|
writeFileSync5(
|
|
8745
8810
|
join20(metaDir, "composition.json"),
|
|
8746
8811
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -8817,7 +8882,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8817
8882
|
}
|
|
8818
8883
|
|
|
8819
8884
|
// src/services/distributed/chunkAudio.ts
|
|
8820
|
-
import { closeSync as closeSync4, existsSync as
|
|
8885
|
+
import { closeSync as closeSync4, existsSync as existsSync16, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8821
8886
|
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8822
8887
|
import { join as join22 } from "node:path";
|
|
8823
8888
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -9290,7 +9355,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9290
9355
|
offset += track.sizes[offsets.length - 1];
|
|
9291
9356
|
}
|
|
9292
9357
|
});
|
|
9293
|
-
const bytes =
|
|
9358
|
+
const bytes = readFileSync8(input.audioPath);
|
|
9294
9359
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
9295
9360
|
mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
|
|
9296
9361
|
input.chunks.forEach((_, index) => {
|
|
@@ -9308,9 +9373,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
9308
9373
|
}
|
|
9309
9374
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9310
9375
|
const layoutPath = join22(planDir, SLICE_LAYOUT);
|
|
9311
|
-
if (!
|
|
9312
|
-
const layout = JSON.parse(
|
|
9313
|
-
const frame =
|
|
9376
|
+
if (!existsSync16(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9377
|
+
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9378
|
+
const frame = readFileSync8(join22(planDir, SLICE_FRAME));
|
|
9314
9379
|
const fd = openSync4(scratchPath, "w");
|
|
9315
9380
|
try {
|
|
9316
9381
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -9323,7 +9388,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
9323
9388
|
);
|
|
9324
9389
|
const start = layout.partStarts[chunkIndex];
|
|
9325
9390
|
if (start !== void 0) {
|
|
9326
|
-
const part =
|
|
9391
|
+
const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
9327
9392
|
writeSync(fd, part, 0, part.length, start);
|
|
9328
9393
|
}
|
|
9329
9394
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -9872,7 +9937,7 @@ function createVideoSourceShipper(input) {
|
|
|
9872
9937
|
let result = null;
|
|
9873
9938
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9874
9939
|
const rel = pathInside(root, srcPath);
|
|
9875
|
-
if (rel !== null && (
|
|
9940
|
+
if (rel !== null && (existsSync17(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9876
9941
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9877
9942
|
break;
|
|
9878
9943
|
}
|
|
@@ -9900,7 +9965,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9900
9965
|
useGpu: false,
|
|
9901
9966
|
browserGpuMode: "software"
|
|
9902
9967
|
});
|
|
9903
|
-
if (!
|
|
9968
|
+
if (!existsSync17(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9904
9969
|
const log = config.logger ?? defaultLogger;
|
|
9905
9970
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9906
9971
|
const abortSignal = config.abortSignal;
|
|
@@ -9930,7 +9995,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9930
9995
|
});
|
|
9931
9996
|
const entryFile = config.entryFile ?? "index.html";
|
|
9932
9997
|
const htmlPath = join24(projectDir, entryFile);
|
|
9933
|
-
if (!
|
|
9998
|
+
if (!existsSync17(htmlPath)) {
|
|
9934
9999
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9935
10000
|
}
|
|
9936
10001
|
const workDir = join24(planDir, ".plan-work");
|
|
@@ -10159,15 +10224,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10159
10224
|
logPlanStage(log, "extract", stageStarted);
|
|
10160
10225
|
const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
|
|
10161
10226
|
const videoFramesDst = join24(planDir, "video-frames");
|
|
10162
|
-
if (
|
|
10227
|
+
if (existsSync17(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10163
10228
|
const videoSourcesDir = join24(planDir, "video-sources");
|
|
10164
10229
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10165
|
-
if (
|
|
10230
|
+
if (existsSync17(stagedVideoFrames)) {
|
|
10166
10231
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10167
10232
|
} else {
|
|
10168
10233
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10169
10234
|
}
|
|
10170
|
-
if (
|
|
10235
|
+
if (existsSync17(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10171
10236
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10172
10237
|
const shipVideoSource = createVideoSourceShipper({
|
|
10173
10238
|
planDir,
|
|
@@ -10287,10 +10352,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10287
10352
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10288
10353
|
}
|
|
10289
10354
|
const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10290
|
-
if (audioResult?.hasAudio &&
|
|
10355
|
+
if (audioResult?.hasAudio && existsSync17(audioResult.audioOutputPath)) {
|
|
10291
10356
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10292
10357
|
}
|
|
10293
|
-
if (chunksCarryAudio &&
|
|
10358
|
+
if (chunksCarryAudio && existsSync17(planAudioPath)) {
|
|
10294
10359
|
if (options.sliceChunkAudio) {
|
|
10295
10360
|
stageStarted = Date.now();
|
|
10296
10361
|
await writeChunkAudioSlices({
|
|
@@ -10403,7 +10468,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10403
10468
|
if (mixed.audioError) {
|
|
10404
10469
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10405
10470
|
}
|
|
10406
|
-
if (!mixed.hasAudio || !
|
|
10471
|
+
if (!mixed.hasAudio || !existsSync17(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10407
10472
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10408
10473
|
return { hasAudio: true };
|
|
10409
10474
|
} finally {
|
|
@@ -10418,11 +10483,11 @@ import {
|
|
|
10418
10483
|
} from "@hyperframes/engine";
|
|
10419
10484
|
import {
|
|
10420
10485
|
copyFileSync as copyFileSync8,
|
|
10421
|
-
existsSync as
|
|
10486
|
+
existsSync as existsSync19,
|
|
10422
10487
|
lstatSync as lstatSync2,
|
|
10423
10488
|
mkdirSync as mkdirSync12,
|
|
10424
10489
|
mkdtempSync as mkdtempSync4,
|
|
10425
|
-
readFileSync as
|
|
10490
|
+
readFileSync as readFileSync9,
|
|
10426
10491
|
readdirSync as readdirSync7,
|
|
10427
10492
|
rmSync as rmSync9,
|
|
10428
10493
|
statSync as statSync7,
|
|
@@ -10436,7 +10501,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
10436
10501
|
// src/services/distributed/planV2Publisher.ts
|
|
10437
10502
|
import {
|
|
10438
10503
|
copyFileSync as copyFileSync7,
|
|
10439
|
-
existsSync as
|
|
10504
|
+
existsSync as existsSync18,
|
|
10440
10505
|
linkSync as linkSync2,
|
|
10441
10506
|
mkdirSync as mkdirSync11,
|
|
10442
10507
|
mkdtempSync as mkdtempSync3,
|
|
@@ -10505,7 +10570,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10505
10570
|
#linkFile;
|
|
10506
10571
|
#committed = false;
|
|
10507
10572
|
constructor(destinationDir, options = {}) {
|
|
10508
|
-
if (
|
|
10573
|
+
if (existsSync18(destinationDir)) {
|
|
10509
10574
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10510
10575
|
}
|
|
10511
10576
|
this.destinationDir = destinationDir;
|
|
@@ -10522,7 +10587,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10522
10587
|
);
|
|
10523
10588
|
}
|
|
10524
10589
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10525
|
-
if (
|
|
10590
|
+
if (existsSync18(destinationPath)) return;
|
|
10526
10591
|
mkdirSync11(dirname12(destinationPath), { recursive: true });
|
|
10527
10592
|
const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10528
10593
|
const temporaryPath = join26(stagingDir, "blob");
|
|
@@ -10540,7 +10605,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10540
10605
|
}
|
|
10541
10606
|
async commitManifest(manifestBytes) {
|
|
10542
10607
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10543
|
-
if (!
|
|
10608
|
+
if (!existsSync18(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10544
10609
|
throw new PlanV2IntegrityError(
|
|
10545
10610
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10546
10611
|
);
|
|
@@ -10571,7 +10636,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
10571
10636
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
10572
10637
|
}
|
|
10573
10638
|
function readJsonFile(path, label) {
|
|
10574
|
-
const contents =
|
|
10639
|
+
const contents = readFileSync9(path, "utf-8");
|
|
10575
10640
|
try {
|
|
10576
10641
|
const parsed = JSON.parse(contents);
|
|
10577
10642
|
return parsed;
|
|
@@ -10619,7 +10684,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10619
10684
|
}
|
|
10620
10685
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10621
10686
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10622
|
-
if (!
|
|
10687
|
+
if (!existsSync19(videoRoot)) return;
|
|
10623
10688
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10624
10689
|
if (!videoEntry.isDirectory()) continue;
|
|
10625
10690
|
const videoDir = join27(videoRoot, videoEntry.name);
|
|
@@ -10720,7 +10785,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10720
10785
|
}
|
|
10721
10786
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10722
10787
|
const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10723
|
-
if (!
|
|
10788
|
+
if (!existsSync19(videosPath)) return;
|
|
10724
10789
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10725
10790
|
for (const video of videos.extracted) {
|
|
10726
10791
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10754,9 +10819,9 @@ function parseChunkSlices(value) {
|
|
|
10754
10819
|
}
|
|
10755
10820
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10756
10821
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10757
|
-
const hasExtractedFrames =
|
|
10822
|
+
const hasExtractedFrames = existsSync19(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10758
10823
|
const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10759
|
-
if (!
|
|
10824
|
+
if (!existsSync19(videosPath)) {
|
|
10760
10825
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10761
10826
|
}
|
|
10762
10827
|
const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10813,7 +10878,7 @@ function computeManifestHash(manifest) {
|
|
|
10813
10878
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10814
10879
|
}
|
|
10815
10880
|
function writeBlob(sourcePath, destinationPath) {
|
|
10816
|
-
if (
|
|
10881
|
+
if (existsSync19(destinationPath)) return;
|
|
10817
10882
|
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
10818
10883
|
const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10819
10884
|
const temporaryPath = join27(temporaryDir, "blob");
|
|
@@ -10853,7 +10918,7 @@ async function putTasks(tasks) {
|
|
|
10853
10918
|
}
|
|
10854
10919
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10855
10920
|
const executionPlanPath = join27(executionPlanDir, "plan.json");
|
|
10856
|
-
if (!
|
|
10921
|
+
if (!existsSync19(executionPlanPath)) {
|
|
10857
10922
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10858
10923
|
}
|
|
10859
10924
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10933,7 +10998,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10933
10998
|
};
|
|
10934
10999
|
}
|
|
10935
11000
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
10936
|
-
if (
|
|
11001
|
+
if (existsSync19(planV2Dir)) {
|
|
10937
11002
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
10938
11003
|
}
|
|
10939
11004
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -11019,7 +11084,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11019
11084
|
}
|
|
11020
11085
|
}
|
|
11021
11086
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11022
|
-
if (
|
|
11087
|
+
if (existsSync19(planV2Dir)) {
|
|
11023
11088
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11024
11089
|
}
|
|
11025
11090
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11173,7 +11238,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11173
11238
|
}
|
|
11174
11239
|
function readPlanV2Manifest(planV2Dir) {
|
|
11175
11240
|
const manifestPath = join27(planV2Dir, "plan.json");
|
|
11176
|
-
if (!
|
|
11241
|
+
if (!existsSync19(manifestPath)) {
|
|
11177
11242
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11178
11243
|
}
|
|
11179
11244
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11192,7 +11257,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11192
11257
|
}
|
|
11193
11258
|
function verifyBlob(planV2Dir, artifact) {
|
|
11194
11259
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11195
|
-
if (!
|
|
11260
|
+
if (!existsSync19(sourcePath)) {
|
|
11196
11261
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11197
11262
|
}
|
|
11198
11263
|
const stats = statSync7(sourcePath);
|
|
@@ -11205,7 +11270,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11205
11270
|
return sourcePath;
|
|
11206
11271
|
}
|
|
11207
11272
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11208
|
-
if (
|
|
11273
|
+
if (existsSync19(destinationDir)) {
|
|
11209
11274
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11210
11275
|
}
|
|
11211
11276
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11270,7 +11335,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11270
11335
|
}
|
|
11271
11336
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11272
11337
|
const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11273
|
-
if (!
|
|
11338
|
+
if (!existsSync19(markerPath)) return null;
|
|
11274
11339
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11275
11340
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11276
11341
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11288,7 +11353,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11288
11353
|
for (const artifact of required) {
|
|
11289
11354
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11290
11355
|
const path = join27(planDir, ...artifact.path.split("/"));
|
|
11291
|
-
if (!
|
|
11356
|
+
if (!existsSync19(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11292
11357
|
throw new PlanV2IntegrityError(
|
|
11293
11358
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11294
11359
|
);
|
|
@@ -11312,10 +11377,10 @@ import {
|
|
|
11312
11377
|
} from "@hyperframes/engine";
|
|
11313
11378
|
import {
|
|
11314
11379
|
cpSync as cpSync3,
|
|
11315
|
-
existsSync as
|
|
11380
|
+
existsSync as existsSync20,
|
|
11316
11381
|
mkdirSync as mkdirSync13,
|
|
11317
11382
|
mkdtempSync as mkdtempSync5,
|
|
11318
|
-
readFileSync as
|
|
11383
|
+
readFileSync as readFileSync10,
|
|
11319
11384
|
readdirSync as readdirSync8,
|
|
11320
11385
|
rmSync as rmSync10,
|
|
11321
11386
|
statSync as statSync8,
|
|
@@ -11367,7 +11432,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11367
11432
|
}
|
|
11368
11433
|
const format = plan.dimensions.format;
|
|
11369
11434
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11370
|
-
if (!
|
|
11435
|
+
if (!existsSync20(dirname14(outputPath))) {
|
|
11371
11436
|
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11372
11437
|
}
|
|
11373
11438
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11391,7 +11456,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11391
11456
|
try {
|
|
11392
11457
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11393
11458
|
const finalPath = join28(workDir, `output.${format}`);
|
|
11394
|
-
const audio = audioPath !== null &&
|
|
11459
|
+
const audio = audioPath !== null && existsSync20(audioPath) ? audioPath : null;
|
|
11395
11460
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11396
11461
|
renameWithRetry11(finalPath, outputPath);
|
|
11397
11462
|
log.info("[assemble] stage timings", {
|
|
@@ -11409,7 +11474,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11409
11474
|
});
|
|
11410
11475
|
}
|
|
11411
11476
|
}
|
|
11412
|
-
const fileSize =
|
|
11477
|
+
const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
|
|
11413
11478
|
return {
|
|
11414
11479
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11415
11480
|
outputPath,
|
|
@@ -11421,16 +11486,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11421
11486
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11422
11487
|
const planJsonPath = join28(planDir, "plan.json");
|
|
11423
11488
|
const chunksJsonPath = join28(planDir, "meta", "chunks.json");
|
|
11424
|
-
if (!
|
|
11489
|
+
if (!existsSync20(planJsonPath)) {
|
|
11425
11490
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11426
11491
|
}
|
|
11427
|
-
const plan = JSON.parse(
|
|
11492
|
+
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11428
11493
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11429
11494
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11430
|
-
if (!
|
|
11495
|
+
if (!existsSync20(chunksJsonPath)) {
|
|
11431
11496
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11432
11497
|
}
|
|
11433
|
-
const chunks = JSON.parse(
|
|
11498
|
+
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
11434
11499
|
if (chunkPaths.length !== chunks.length) {
|
|
11435
11500
|
throw new Error(
|
|
11436
11501
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -11442,7 +11507,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11442
11507
|
}
|
|
11443
11508
|
}
|
|
11444
11509
|
for (const path of chunkPaths.flat()) {
|
|
11445
|
-
if (!
|
|
11510
|
+
if (!existsSync20(path)) {
|
|
11446
11511
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11447
11512
|
}
|
|
11448
11513
|
}
|
|
@@ -11576,13 +11641,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11576
11641
|
return lockedAudio(planDir).separateAudio === true;
|
|
11577
11642
|
}
|
|
11578
11643
|
function planFormat(planDir) {
|
|
11579
|
-
const plan = JSON.parse(
|
|
11644
|
+
const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
|
|
11580
11645
|
return plan.dimensions.format;
|
|
11581
11646
|
}
|
|
11582
11647
|
function lockedAudio(planDir) {
|
|
11583
11648
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11584
|
-
if (!
|
|
11585
|
-
return JSON.parse(
|
|
11649
|
+
if (!existsSync20(encoderJsonPath)) return {};
|
|
11650
|
+
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11586
11651
|
}
|
|
11587
11652
|
function readCfrEncoder(planDir, format) {
|
|
11588
11653
|
if (format !== "mp4") {
|
|
@@ -11591,10 +11656,10 @@ function readCfrEncoder(planDir, format) {
|
|
|
11591
11656
|
);
|
|
11592
11657
|
}
|
|
11593
11658
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11594
|
-
if (!
|
|
11659
|
+
if (!existsSync20(encoderJsonPath)) {
|
|
11595
11660
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11596
11661
|
}
|
|
11597
|
-
const encoderJson = JSON.parse(
|
|
11662
|
+
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11598
11663
|
if (encoderJson.encoder === "libx265-software") {
|
|
11599
11664
|
throw new Error(
|
|
11600
11665
|
`[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".`
|
|
@@ -11603,7 +11668,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11603
11668
|
return encoderJson;
|
|
11604
11669
|
}
|
|
11605
11670
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11606
|
-
if (
|
|
11671
|
+
if (existsSync20(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11607
11672
|
mkdirSync13(outputPath, { recursive: true });
|
|
11608
11673
|
let globalIdx = 0;
|
|
11609
11674
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11627,7 +11692,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11627
11692
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11628
11693
|
);
|
|
11629
11694
|
}
|
|
11630
|
-
if (audioPath !== null &&
|
|
11695
|
+
if (audioPath !== null && existsSync20(audioPath)) {
|
|
11631
11696
|
const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11632
11697
|
cpSync3(audioPath, sidecar);
|
|
11633
11698
|
}
|
|
@@ -11648,7 +11713,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11648
11713
|
|
|
11649
11714
|
// src/services/distributed/renderChunk.ts
|
|
11650
11715
|
import { randomBytes } from "node:crypto";
|
|
11651
|
-
import { existsSync as
|
|
11716
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11652
11717
|
import { cpus } from "node:os";
|
|
11653
11718
|
import { join as join29 } from "node:path";
|
|
11654
11719
|
import {
|
|
@@ -11771,10 +11836,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
11771
11836
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
11772
11837
|
}
|
|
11773
11838
|
function hashChunkOutput(outputPath, kind) {
|
|
11774
|
-
if (kind === "file") return sha256Hex(
|
|
11839
|
+
if (kind === "file") return sha256Hex(readFileSync11(outputPath));
|
|
11775
11840
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11776
11841
|
const lines = entries.map(
|
|
11777
|
-
(name) => `${name}\0${sha256Hex(
|
|
11842
|
+
(name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
|
|
11778
11843
|
);
|
|
11779
11844
|
return sha256Hex(lines.join("\0"));
|
|
11780
11845
|
}
|
|
@@ -11794,13 +11859,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11794
11859
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11795
11860
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11796
11861
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11797
|
-
if (!
|
|
11862
|
+
if (!existsSync21(planJsonPath)) {
|
|
11798
11863
|
throw new RenderChunkValidationError(
|
|
11799
11864
|
MISSING_PLAN_ARTIFACT,
|
|
11800
11865
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
11801
11866
|
);
|
|
11802
11867
|
}
|
|
11803
|
-
const plan = JSON.parse(
|
|
11868
|
+
const plan = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
11804
11869
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
11805
11870
|
const planHashStarted = Date.now();
|
|
11806
11871
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -11810,20 +11875,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11810
11875
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11811
11876
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11812
11877
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11813
|
-
if (!
|
|
11878
|
+
if (!existsSync21(required)) {
|
|
11814
11879
|
throw new RenderChunkValidationError(
|
|
11815
11880
|
MISSING_PLAN_ARTIFACT,
|
|
11816
11881
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
11817
11882
|
);
|
|
11818
11883
|
}
|
|
11819
11884
|
}
|
|
11820
|
-
const encoder = JSON.parse(
|
|
11821
|
-
const chunks = JSON.parse(
|
|
11885
|
+
const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
11886
|
+
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11822
11887
|
const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11823
11888
|
let planVideos = null;
|
|
11824
|
-
if (
|
|
11889
|
+
if (existsSync21(videosJsonPath)) {
|
|
11825
11890
|
try {
|
|
11826
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
11891
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11827
11892
|
} catch (err) {
|
|
11828
11893
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
11829
11894
|
throw new RenderChunkValidationError(
|
|
@@ -11852,7 +11917,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11852
11917
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11853
11918
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11854
11919
|
const compiledDir = join29(planDir, "compiled");
|
|
11855
|
-
if (!
|
|
11920
|
+
if (!existsSync21(compiledDir)) {
|
|
11856
11921
|
throw new RenderChunkValidationError(
|
|
11857
11922
|
MISSING_PLAN_ARTIFACT,
|
|
11858
11923
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -11934,7 +11999,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11934
11999
|
dimensions: plan.dimensions,
|
|
11935
12000
|
encoder,
|
|
11936
12001
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11937
|
-
html:
|
|
12002
|
+
html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
|
|
11938
12003
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
11939
12004
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
11940
12005
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -12102,10 +12167,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12102
12167
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12103
12168
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12104
12169
|
if (isPngSequence) {
|
|
12105
|
-
if (!
|
|
12170
|
+
if (!existsSync21(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12106
12171
|
} else {
|
|
12107
12172
|
const outDir = join29(outputChunkPath, "..");
|
|
12108
|
-
if (!
|
|
12173
|
+
if (!existsSync21(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12109
12174
|
}
|
|
12110
12175
|
const encodeInput = {
|
|
12111
12176
|
job,
|