@hyperframes/producer 0.8.192 → 0.8.193
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.d.ts +1 -0
- package/dist/distributed.js +258 -108
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +51 -51
- package/dist/index.js +490 -458
- package/dist/public-server.js +319 -299
- package/dist/services/distributed/plan.d.ts +2 -0
- package/dist/services/distributed/probeFacts.d.ts +4 -0
- package/dist/services/render/perfSummary.d.ts +6 -0
- package/dist/services/render/stages/audioStage.d.ts +3 -1
- package/dist/services/renderOrchestrator.d.ts +2 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
existsSync as existsSync15,
|
|
7
7
|
linkSync,
|
|
8
8
|
mkdirSync as mkdirSync10,
|
|
9
|
-
renameSync as renameSync4,
|
|
10
9
|
rmSync as rmSync9,
|
|
11
10
|
writeFileSync as writeFileSync6
|
|
12
11
|
} from "node:fs";
|
|
@@ -19,7 +18,9 @@ import {
|
|
|
19
18
|
getEncoderPreset as getEncoderPreset4,
|
|
20
19
|
normalizeVp9CpuUsed,
|
|
21
20
|
resolveConfig as resolveConfig3,
|
|
22
|
-
declareContentAddressedUrl
|
|
21
|
+
declareContentAddressedUrl,
|
|
22
|
+
renameWithRetry as renameWithRetry6,
|
|
23
|
+
declareProbeFactsUrl
|
|
23
24
|
} from "@hyperframes/engine";
|
|
24
25
|
|
|
25
26
|
// src/logger.ts
|
|
@@ -1150,7 +1151,7 @@ function encoderFailureError(prefix, result) {
|
|
|
1150
1151
|
}
|
|
1151
1152
|
|
|
1152
1153
|
// src/services/render/artifactTransaction.ts
|
|
1153
|
-
import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST } from "@hyperframes/engine";
|
|
1154
|
+
import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
|
|
1154
1155
|
|
|
1155
1156
|
// src/utils/ffprobe.ts
|
|
1156
1157
|
import {
|
|
@@ -1227,6 +1228,24 @@ import {
|
|
|
1227
1228
|
|
|
1228
1229
|
// src/services/render/captureStageError.ts
|
|
1229
1230
|
import { CaptureFailure, classifyCaptureFailure } from "@hyperframes/engine";
|
|
1231
|
+
|
|
1232
|
+
// src/services/render/perfSummary.ts
|
|
1233
|
+
import { fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
|
|
1234
|
+
function pushWorkerDedupPerfs(results, sink) {
|
|
1235
|
+
for (const r of results) {
|
|
1236
|
+
if (r.perf) sink.push(r.perf);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function pushWorkerPageErrors(results, sink) {
|
|
1240
|
+
for (const result of results) {
|
|
1241
|
+
for (const line of result.pageErrorLines ?? []) if (!sink.includes(line)) sink.push(line);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function withWorkerPageErrors(consoleLines, workerLines) {
|
|
1245
|
+
return [...consoleLines, ...workerLines.filter((line) => !consoleLines.includes(line))];
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/services/render/captureStageError.ts
|
|
1230
1249
|
var CaptureStageError = class extends CaptureFailure {
|
|
1231
1250
|
browserConsole;
|
|
1232
1251
|
constructor(input) {
|
|
@@ -1235,10 +1254,13 @@ var CaptureStageError = class extends CaptureFailure {
|
|
|
1235
1254
|
kind: classified.kind,
|
|
1236
1255
|
message: normalizeErrorMessage(input.cause),
|
|
1237
1256
|
cause: input.cause,
|
|
1238
|
-
workerDiagnostics: classified.workerDiagnostics
|
|
1257
|
+
workerDiagnostics: classified.workerDiagnostics,
|
|
1258
|
+
pageErrorLines: classified.pageErrorLines
|
|
1239
1259
|
});
|
|
1240
1260
|
this.name = "CaptureStageError";
|
|
1241
|
-
this.browserConsole = input.browserConsole
|
|
1261
|
+
this.browserConsole = withWorkerPageErrors(input.browserConsole, [
|
|
1262
|
+
...classified.pageErrorLines
|
|
1263
|
+
]);
|
|
1242
1264
|
if (input.cause instanceof Error && input.cause.stack) {
|
|
1243
1265
|
this.stack = input.cause.stack;
|
|
1244
1266
|
}
|
|
@@ -1298,7 +1320,7 @@ import {
|
|
|
1298
1320
|
resolveVideoExtractionWindow,
|
|
1299
1321
|
runFfmpeg
|
|
1300
1322
|
} from "@hyperframes/engine";
|
|
1301
|
-
import { fpsToFfmpegArg, fpsToNumber as
|
|
1323
|
+
import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
1302
1324
|
var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
|
|
1303
1325
|
var BYTES_PER_MIB = 1024 * 1024;
|
|
1304
1326
|
|
|
@@ -1324,13 +1346,8 @@ import {
|
|
|
1324
1346
|
spawnStreamingEncoder
|
|
1325
1347
|
} from "@hyperframes/engine";
|
|
1326
1348
|
|
|
1327
|
-
// src/services/render/
|
|
1328
|
-
import {
|
|
1329
|
-
function pushWorkerDedupPerfs(results, sink) {
|
|
1330
|
-
for (const r of results) {
|
|
1331
|
-
if (r.perf) sink.push(r.perf);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1349
|
+
// src/services/render/segmentManifest.ts
|
|
1350
|
+
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
1334
1351
|
|
|
1335
1352
|
// src/services/render/hdrMode.ts
|
|
1336
1353
|
import {
|
|
@@ -1684,19 +1701,11 @@ import {
|
|
|
1684
1701
|
|
|
1685
1702
|
// src/services/animatedGifPrep.ts
|
|
1686
1703
|
import { createHash as createHash2 } from "node:crypto";
|
|
1687
|
-
import {
|
|
1688
|
-
copyFileSync as copyFileSync2,
|
|
1689
|
-
existsSync as existsSync4,
|
|
1690
|
-
mkdirSync as mkdirSync3,
|
|
1691
|
-
readFileSync as readFileSync3,
|
|
1692
|
-
renameSync,
|
|
1693
|
-
rmSync as rmSync3,
|
|
1694
|
-
statSync as statSync2
|
|
1695
|
-
} from "node:fs";
|
|
1704
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync2 } from "node:fs";
|
|
1696
1705
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve4 } from "node:path";
|
|
1697
1706
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
1698
1707
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
1699
|
-
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2 } from "@hyperframes/engine";
|
|
1708
|
+
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
1700
1709
|
var PREPARED_GIF_SUBDIR = "_animated_gif";
|
|
1701
1710
|
var CACHE_SCHEMA = "hfgif-v1";
|
|
1702
1711
|
function splitUrlSuffix(src) {
|
|
@@ -1875,7 +1884,7 @@ async function ensurePreparedWebm(input) {
|
|
|
1875
1884
|
throw new Error("Animated GIF transcode produced an empty output");
|
|
1876
1885
|
}
|
|
1877
1886
|
if (!isUsableFile(input.cachePath)) {
|
|
1878
|
-
|
|
1887
|
+
renameWithRetry3(tmpPath, input.cachePath);
|
|
1879
1888
|
} else {
|
|
1880
1889
|
rmSync3(tmpPath, { force: true });
|
|
1881
1890
|
}
|
|
@@ -5007,7 +5016,7 @@ import {
|
|
|
5007
5016
|
|
|
5008
5017
|
// src/services/render/audioPadTrim.ts
|
|
5009
5018
|
import { spawn } from "node:child_process";
|
|
5010
|
-
import { mkdtempSync as mkdtempSync2,
|
|
5019
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
|
|
5011
5020
|
import { dirname as dirname5, join as join9 } from "node:path";
|
|
5012
5021
|
import {
|
|
5013
5022
|
DEFAULT_CONFIG,
|
|
@@ -5016,6 +5025,7 @@ import {
|
|
|
5016
5025
|
extractAudioMetadata as extractAudioMetadata3,
|
|
5017
5026
|
getFfprobeBinary,
|
|
5018
5027
|
ManagedChildProcess,
|
|
5028
|
+
renameWithRetry as renameWithRetry4,
|
|
5019
5029
|
runFfmpeg as runFfmpeg4,
|
|
5020
5030
|
trackChildProcess
|
|
5021
5031
|
} from "@hyperframes/engine";
|
|
@@ -5271,7 +5281,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5271
5281
|
}
|
|
5272
5282
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
5273
5283
|
if (measuredPath !== correctedPath) return { success: true };
|
|
5274
|
-
|
|
5284
|
+
renameWithRetry4(correctedPath, input.audioPath);
|
|
5275
5285
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
5276
5286
|
}
|
|
5277
5287
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -5461,6 +5471,7 @@ async function runAudioStage(input) {
|
|
|
5461
5471
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
5462
5472
|
let audioLoweredDb;
|
|
5463
5473
|
if (result.hasAudio) {
|
|
5474
|
+
const guardStart = Date.now();
|
|
5464
5475
|
const peak = await constrainAacTruePeak({
|
|
5465
5476
|
audioPath: result.audioOutputPath,
|
|
5466
5477
|
sourcePath: pcmSourcePath,
|
|
@@ -5470,6 +5481,7 @@ async function runAudioStage(input) {
|
|
|
5470
5481
|
});
|
|
5471
5482
|
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
5472
5483
|
audioLoweredDb = peak.loweredDb;
|
|
5484
|
+
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
5473
5485
|
}
|
|
5474
5486
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
5475
5487
|
} finally {
|
|
@@ -5495,6 +5507,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5495
5507
|
let hasAudio = false;
|
|
5496
5508
|
let audioError;
|
|
5497
5509
|
let audioFailures;
|
|
5510
|
+
let audioPhaseMs;
|
|
5498
5511
|
if (audios.length > 0) {
|
|
5499
5512
|
let audioResult;
|
|
5500
5513
|
try {
|
|
@@ -5544,6 +5557,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5544
5557
|
assertAudioNotInterrupted(audioResult.failures);
|
|
5545
5558
|
hasAudio = audioResult.success;
|
|
5546
5559
|
audioFailures = audioResult.failures;
|
|
5560
|
+
audioPhaseMs = audioResult.phaseMs;
|
|
5547
5561
|
if (!hasAudio) {
|
|
5548
5562
|
audioError = audioResult.error ?? "audio mix failed for an unknown reason";
|
|
5549
5563
|
for (const failure of audioResult.failures ?? []) {
|
|
@@ -5562,7 +5576,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5562
5576
|
audioOutputPath,
|
|
5563
5577
|
hasAudio,
|
|
5564
5578
|
audioError,
|
|
5565
|
-
audioFailures
|
|
5579
|
+
audioFailures,
|
|
5580
|
+
audioPhaseMs
|
|
5566
5581
|
};
|
|
5567
5582
|
}
|
|
5568
5583
|
|
|
@@ -5672,6 +5687,7 @@ async function runCaptureStage(input) {
|
|
|
5672
5687
|
let { workerCount } = plan2;
|
|
5673
5688
|
const { forceScreenshot } = plan2;
|
|
5674
5689
|
let lastBrowserConsole = [];
|
|
5690
|
+
const workerPageErrors = [];
|
|
5675
5691
|
let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
|
|
5676
5692
|
const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
|
|
5677
5693
|
if (frameRange !== void 0) {
|
|
@@ -5709,6 +5725,7 @@ async function runCaptureStage(input) {
|
|
|
5709
5725
|
abortSignal,
|
|
5710
5726
|
frameRangeStart: frameRange?.startFrame,
|
|
5711
5727
|
dedupPerfs,
|
|
5728
|
+
pageErrorLines: workerPageErrors,
|
|
5712
5729
|
onProgress: (progress) => {
|
|
5713
5730
|
if (progress.latestWorkerPhase) {
|
|
5714
5731
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -5729,6 +5746,11 @@ async function runCaptureStage(input) {
|
|
|
5729
5746
|
},
|
|
5730
5747
|
cfg: captureCfg,
|
|
5731
5748
|
log
|
|
5749
|
+
}).catch((error) => {
|
|
5750
|
+
throw wrapCaptureStageError(
|
|
5751
|
+
error,
|
|
5752
|
+
withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
|
|
5753
|
+
);
|
|
5732
5754
|
});
|
|
5733
5755
|
captureAttempts.push(...attempts);
|
|
5734
5756
|
const lastAttempt = attempts[attempts.length - 1];
|
|
@@ -5741,6 +5763,7 @@ async function runCaptureStage(input) {
|
|
|
5741
5763
|
await closeCaptureSession5(probeSession);
|
|
5742
5764
|
probeSession = null;
|
|
5743
5765
|
}
|
|
5766
|
+
lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
|
|
5744
5767
|
} else {
|
|
5745
5768
|
const sequential = await runSequentialDiskCapture(
|
|
5746
5769
|
input,
|
|
@@ -5948,6 +5971,7 @@ import {
|
|
|
5948
5971
|
statSync as statSync3,
|
|
5949
5972
|
writeFileSync as writeFileSync2
|
|
5950
5973
|
} from "node:fs";
|
|
5974
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
5951
5975
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
5952
5976
|
import {
|
|
5953
5977
|
encodeFramesChunkedConcat,
|
|
@@ -6117,6 +6141,100 @@ function videoEncoderOptions(input, engineCfg) {
|
|
|
6117
6141
|
gopSize: input.gopSize
|
|
6118
6142
|
};
|
|
6119
6143
|
}
|
|
6144
|
+
function canEncodeWhileCapturing(input, workerCount, cpuCount) {
|
|
6145
|
+
return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg";
|
|
6146
|
+
}
|
|
6147
|
+
async function readLandedFrame(workerPath, mergedPath) {
|
|
6148
|
+
if (workerPath !== void 0) {
|
|
6149
|
+
try {
|
|
6150
|
+
return await readFile2(workerPath);
|
|
6151
|
+
} catch (error) {
|
|
6152
|
+
if (error.code !== "ENOENT") throw error;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
return readFile2(mergedPath);
|
|
6156
|
+
}
|
|
6157
|
+
function encodeWhileCapturing(input, totalFrames) {
|
|
6158
|
+
const engineCfg = resolveEngineConfig(input);
|
|
6159
|
+
const stop = new AbortController();
|
|
6160
|
+
const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, stop.signal]) : stop.signal;
|
|
6161
|
+
const landed = /* @__PURE__ */ new Map();
|
|
6162
|
+
let captureDone = false;
|
|
6163
|
+
let framesFed = 0;
|
|
6164
|
+
let wake = null;
|
|
6165
|
+
const nudge = () => {
|
|
6166
|
+
wake?.();
|
|
6167
|
+
wake = null;
|
|
6168
|
+
};
|
|
6169
|
+
signal.addEventListener("abort", nudge, { once: true });
|
|
6170
|
+
let reportFed = null;
|
|
6171
|
+
const encoder = spawnStreamingEncoder4(
|
|
6172
|
+
input.videoOnlyPath,
|
|
6173
|
+
{ ...videoEncoderOptions(input, engineCfg), imageFormat: "jpeg" },
|
|
6174
|
+
signal,
|
|
6175
|
+
engineCfg
|
|
6176
|
+
);
|
|
6177
|
+
const fed = (async () => {
|
|
6178
|
+
const stream = await encoder;
|
|
6179
|
+
for (let next = 0; next < totalFrames; next++) {
|
|
6180
|
+
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
6181
|
+
await new Promise((resolve9) => wake = resolve9);
|
|
6182
|
+
}
|
|
6183
|
+
if (signal.aborted) return;
|
|
6184
|
+
const mergedPath = join12(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
6185
|
+
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
6186
|
+
landed.delete(next);
|
|
6187
|
+
if (!await stream.writeFrame(frame)) {
|
|
6188
|
+
throw new Error(stream.getExitError() ?? "encoder exited before the last frame");
|
|
6189
|
+
}
|
|
6190
|
+
framesFed++;
|
|
6191
|
+
reportFed?.(framesFed);
|
|
6192
|
+
}
|
|
6193
|
+
})().then(
|
|
6194
|
+
() => void 0,
|
|
6195
|
+
(error) => error ?? new Error("encode failed")
|
|
6196
|
+
);
|
|
6197
|
+
const closeEncoder = async () => (await encoder.catch(() => null))?.close();
|
|
6198
|
+
return {
|
|
6199
|
+
onFrameLanded: (frame) => {
|
|
6200
|
+
landed.set(frame.index, frame.path);
|
|
6201
|
+
nudge();
|
|
6202
|
+
},
|
|
6203
|
+
finish: async () => {
|
|
6204
|
+
const start = Date.now();
|
|
6205
|
+
captureDone = true;
|
|
6206
|
+
input.log.info("[Render] Encoded frames while capturing", {
|
|
6207
|
+
framesFed,
|
|
6208
|
+
totalFrames
|
|
6209
|
+
});
|
|
6210
|
+
const encoded = startEncodeProgress(input.job, "Encoding video", input.onProgress);
|
|
6211
|
+
reportFed = encoded;
|
|
6212
|
+
nudge();
|
|
6213
|
+
const failure = await fed;
|
|
6214
|
+
const result = await closeEncoder();
|
|
6215
|
+
input.assertNotAborted();
|
|
6216
|
+
if (failure === void 0 && result?.success) {
|
|
6217
|
+
encoded();
|
|
6218
|
+
return { encodeMs: Date.now() - start };
|
|
6219
|
+
}
|
|
6220
|
+
input.log.warn(
|
|
6221
|
+
"[Render] Encoding while capturing failed; encoding the frames after capture",
|
|
6222
|
+
{
|
|
6223
|
+
error: String(failure ?? result?.error)
|
|
6224
|
+
}
|
|
6225
|
+
);
|
|
6226
|
+
return runEncodeStage(input);
|
|
6227
|
+
},
|
|
6228
|
+
abort: async () => {
|
|
6229
|
+
stop.abort();
|
|
6230
|
+
await fed;
|
|
6231
|
+
await closeEncoder();
|
|
6232
|
+
},
|
|
6233
|
+
get framesFed() {
|
|
6234
|
+
return framesFed;
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
}
|
|
6120
6238
|
async function runEncodeStage(input) {
|
|
6121
6239
|
const {
|
|
6122
6240
|
job,
|
|
@@ -6430,6 +6548,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6430
6548
|
options.cfg
|
|
6431
6549
|
);
|
|
6432
6550
|
pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
|
|
6551
|
+
if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
|
|
6433
6552
|
} finally {
|
|
6434
6553
|
await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
|
|
6435
6554
|
}
|
|
@@ -7508,15 +7627,9 @@ import { join as join19 } from "node:path";
|
|
|
7508
7627
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
7509
7628
|
|
|
7510
7629
|
// src/services/distributed/mp4Stitch.ts
|
|
7511
|
-
import {
|
|
7512
|
-
createReadStream as createReadStream4,
|
|
7513
|
-
createWriteStream,
|
|
7514
|
-
mkdirSync as mkdirSync8,
|
|
7515
|
-
mkdtempSync as mkdtempSync3,
|
|
7516
|
-
renameSync as renameSync3,
|
|
7517
|
-
rmSync as rmSync8
|
|
7518
|
-
} from "node:fs";
|
|
7630
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
|
|
7519
7631
|
import { open } from "node:fs/promises";
|
|
7632
|
+
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
7520
7633
|
import { dirname as dirname9, join as join18 } from "node:path";
|
|
7521
7634
|
import { finished, pipeline } from "node:stream/promises";
|
|
7522
7635
|
function boxAt(src, off, at, limit) {
|
|
@@ -7889,7 +8002,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
7889
8002
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
7890
8003
|
out.end();
|
|
7891
8004
|
await finished(out);
|
|
7892
|
-
|
|
8005
|
+
renameWithRetry5(join18(workDir, "out.mp4"), outputPath);
|
|
7893
8006
|
} finally {
|
|
7894
8007
|
out.destroy();
|
|
7895
8008
|
rmSync8(workDir, { recursive: true, force: true });
|
|
@@ -8619,7 +8732,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
8619
8732
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
8620
8733
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
8621
8734
|
const remoteLocalPath = (rel) => join21(projectDir, ...rel.split("/"));
|
|
8622
|
-
for (const [, file] of remoteFiles)
|
|
8735
|
+
for (const [, file] of remoteFiles) {
|
|
8736
|
+
declareContentAddressedUrl(file.url, file.sha256);
|
|
8737
|
+
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
8738
|
+
}
|
|
8623
8739
|
const urlByAbsentLocalPath = new Map(
|
|
8624
8740
|
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
8625
8741
|
);
|
|
@@ -8741,7 +8857,8 @@ function mixPlanAudio(c, compiledDir) {
|
|
|
8741
8857
|
audios: c.composition.audios,
|
|
8742
8858
|
masterFxChain: c.composition.masterFxChain,
|
|
8743
8859
|
abortSignal: c.abortSignal,
|
|
8744
|
-
assertNotAborted: c.assertNotAborted
|
|
8860
|
+
assertNotAborted: c.assertNotAborted,
|
|
8861
|
+
log: c.log
|
|
8745
8862
|
});
|
|
8746
8863
|
}
|
|
8747
8864
|
async function extractPlanVideos(c, deferRangeExtraction) {
|
|
@@ -8760,6 +8877,7 @@ async function extractPlanVideos(c, deferRangeExtraction) {
|
|
|
8760
8877
|
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
8761
8878
|
if (extractResult.extractionResult) {
|
|
8762
8879
|
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
8880
|
+
c.log.info("[plan] extract phases", { ...extractResult.extractionResult.phaseBreakdown });
|
|
8763
8881
|
}
|
|
8764
8882
|
return extractResult;
|
|
8765
8883
|
}
|
|
@@ -8805,12 +8923,12 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8805
8923
|
const videoSourcesDir = join21(planDir, "video-sources");
|
|
8806
8924
|
rmSync9(videoSourcesDir, { recursive: true, force: true });
|
|
8807
8925
|
if (existsSync15(stagedVideoFrames)) {
|
|
8808
|
-
|
|
8926
|
+
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
8809
8927
|
} else {
|
|
8810
8928
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
8811
8929
|
}
|
|
8812
8930
|
if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
|
|
8813
|
-
|
|
8931
|
+
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
8814
8932
|
const shipVideoSource = createVideoSourceShipper({
|
|
8815
8933
|
planDir,
|
|
8816
8934
|
projectDir,
|
|
@@ -8913,7 +9031,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8913
9031
|
}
|
|
8914
9032
|
const planAudioPath = join21(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
8915
9033
|
if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
|
|
8916
|
-
|
|
9034
|
+
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
8917
9035
|
}
|
|
8918
9036
|
if (chunksCarryAudio && existsSync15(planAudioPath)) {
|
|
8919
9037
|
if (options.sliceChunkAudio) {
|
|
@@ -9027,7 +9145,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
9027
9145
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
9028
9146
|
}
|
|
9029
9147
|
if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
|
|
9030
|
-
|
|
9148
|
+
renameWithRetry6(mixed.audioOutputPath, outputPath);
|
|
9031
9149
|
return { hasAudio: true };
|
|
9032
9150
|
} finally {
|
|
9033
9151
|
rmSync9(c.workDir, { recursive: true, force: true });
|
|
@@ -9059,7 +9177,6 @@ import {
|
|
|
9059
9177
|
mkdtempSync as mkdtempSync5,
|
|
9060
9178
|
readFileSync as readFileSync10,
|
|
9061
9179
|
readdirSync as readdirSync7,
|
|
9062
|
-
renameSync as renameSync6,
|
|
9063
9180
|
rmSync as rmSync11,
|
|
9064
9181
|
statSync as statSync6,
|
|
9065
9182
|
writeFileSync as writeFileSync8
|
|
@@ -9067,7 +9184,7 @@ import {
|
|
|
9067
9184
|
import { tmpdir } from "node:os";
|
|
9068
9185
|
import { dirname as dirname11, isAbsolute as isAbsolute4, join as join24, relative as relative6, resolve as resolve8, sep as sep3 } from "node:path";
|
|
9069
9186
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
9070
|
-
import { createFrameLookupTable as createFrameLookupTable4 } from "@hyperframes/engine";
|
|
9187
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
9071
9188
|
|
|
9072
9189
|
// src/services/distributed/planV2Publisher.ts
|
|
9073
9190
|
import {
|
|
@@ -9076,11 +9193,11 @@ import {
|
|
|
9076
9193
|
linkSync as linkSync2,
|
|
9077
9194
|
mkdirSync as mkdirSync11,
|
|
9078
9195
|
mkdtempSync as mkdtempSync4,
|
|
9079
|
-
renameSync as renameSync5,
|
|
9080
9196
|
rmSync as rmSync10,
|
|
9081
9197
|
statSync as statSync5,
|
|
9082
9198
|
writeFileSync as writeFileSync7
|
|
9083
9199
|
} from "node:fs";
|
|
9200
|
+
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
9084
9201
|
import { dirname as dirname10, join as join23 } from "node:path";
|
|
9085
9202
|
|
|
9086
9203
|
// src/services/distributed/planV2Errors.ts
|
|
@@ -9169,7 +9286,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9169
9286
|
if (!canFallbackToCopy(error)) throw error;
|
|
9170
9287
|
copyFileSync6(blob.sourcePath, temporaryPath);
|
|
9171
9288
|
}
|
|
9172
|
-
|
|
9289
|
+
renameWithRetry7(temporaryPath, destinationPath);
|
|
9173
9290
|
} finally {
|
|
9174
9291
|
rmSync10(stagingDir, { recursive: true, force: true });
|
|
9175
9292
|
}
|
|
@@ -9183,7 +9300,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9183
9300
|
}
|
|
9184
9301
|
}
|
|
9185
9302
|
writeFileSync7(join23(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
9186
|
-
|
|
9303
|
+
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
9187
9304
|
this.#committed = true;
|
|
9188
9305
|
}
|
|
9189
9306
|
async abort() {
|
|
@@ -9450,7 +9567,7 @@ function writeBlob(sourcePath, destinationPath) {
|
|
|
9450
9567
|
const temporaryPath = join24(temporaryDir, "blob");
|
|
9451
9568
|
try {
|
|
9452
9569
|
copyFileSync7(sourcePath, temporaryPath);
|
|
9453
|
-
|
|
9570
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
9454
9571
|
} finally {
|
|
9455
9572
|
rmSync11(temporaryDir, { recursive: true, force: true });
|
|
9456
9573
|
}
|
|
@@ -9582,7 +9699,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
9582
9699
|
canonicalJsonStringify(publication.manifest),
|
|
9583
9700
|
"utf-8"
|
|
9584
9701
|
);
|
|
9585
|
-
|
|
9702
|
+
renameWithRetry8(tempDir, planV2Dir);
|
|
9586
9703
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
9587
9704
|
} catch (error) {
|
|
9588
9705
|
rmSync11(tempDir, { recursive: true, force: true });
|
|
@@ -9867,7 +9984,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
9867
9984
|
}),
|
|
9868
9985
|
"utf-8"
|
|
9869
9986
|
);
|
|
9870
|
-
|
|
9987
|
+
renameWithRetry8(tempDir, destinationDir);
|
|
9871
9988
|
} catch (error) {
|
|
9872
9989
|
rmSync11(tempDir, { recursive: true, force: true });
|
|
9873
9990
|
throw error;
|
|
@@ -9945,7 +10062,6 @@ import {
|
|
|
9945
10062
|
mkdtempSync as mkdtempSync6,
|
|
9946
10063
|
readFileSync as readFileSync11,
|
|
9947
10064
|
readdirSync as readdirSync8,
|
|
9948
|
-
renameSync as renameSync7,
|
|
9949
10065
|
rmSync as rmSync12,
|
|
9950
10066
|
statSync as statSync7,
|
|
9951
10067
|
writeFileSync as writeFileSync9
|
|
@@ -9959,7 +10075,8 @@ import {
|
|
|
9959
10075
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
9960
10076
|
resolveConfig as resolveConfig4,
|
|
9961
10077
|
runFfmpeg as runFfmpeg7,
|
|
9962
|
-
appendSoftwareEncoderThreadsArg
|
|
10078
|
+
appendSoftwareEncoderThreadsArg,
|
|
10079
|
+
renameWithRetry as renameWithRetry9
|
|
9963
10080
|
} from "@hyperframes/engine";
|
|
9964
10081
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
9965
10082
|
function cfrRateControlArgs(locked) {
|
|
@@ -10016,7 +10133,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10016
10133
|
const finalPath = join25(workDir, `output.${format}`);
|
|
10017
10134
|
const audio = audioPath !== null && existsSync18(audioPath) ? audioPath : null;
|
|
10018
10135
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
10019
|
-
|
|
10136
|
+
renameWithRetry9(finalPath, outputPath);
|
|
10020
10137
|
log.info("[assemble] stage timings", {
|
|
10021
10138
|
...stageMs,
|
|
10022
10139
|
chunks: chunkPaths.length,
|
|
@@ -10268,6 +10385,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10268
10385
|
// src/services/distributed/renderChunk.ts
|
|
10269
10386
|
import { randomBytes } from "node:crypto";
|
|
10270
10387
|
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync12, readdirSync as readdirSync9, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10388
|
+
import { cpus } from "node:os";
|
|
10271
10389
|
import { join as join26 } from "node:path";
|
|
10272
10390
|
import {
|
|
10273
10391
|
assertSwiftShader,
|
|
@@ -10572,6 +10690,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10572
10690
|
let forceScreenshotForChunk = encoder.forceScreenshot;
|
|
10573
10691
|
let shownBoxes;
|
|
10574
10692
|
let extraction;
|
|
10693
|
+
const overlap = { current: null };
|
|
10575
10694
|
try {
|
|
10576
10695
|
if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
|
|
10577
10696
|
const bootStarted = Date.now();
|
|
@@ -10659,6 +10778,59 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10659
10778
|
videoFrameLookup,
|
|
10660
10779
|
frameCacheOptions(cfg, chunkWorkerCount)
|
|
10661
10780
|
);
|
|
10781
|
+
const isPngSequence = plan2.dimensions.format === "png-sequence";
|
|
10782
|
+
outputKind = isPngSequence ? "frame-dir" : "file";
|
|
10783
|
+
const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
|
|
10784
|
+
const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
|
|
10785
|
+
const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
|
|
10786
|
+
const effectiveQuality = encoder.crf ?? preset.quality;
|
|
10787
|
+
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
10788
|
+
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
10789
|
+
if (isPngSequence) {
|
|
10790
|
+
if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
10791
|
+
} else {
|
|
10792
|
+
const outDir = join26(outputChunkPath, "..");
|
|
10793
|
+
if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
10794
|
+
}
|
|
10795
|
+
const encodeInput = {
|
|
10796
|
+
job,
|
|
10797
|
+
log,
|
|
10798
|
+
outputPath: videoOnlyPath,
|
|
10799
|
+
framesDir,
|
|
10800
|
+
videoOnlyPath,
|
|
10801
|
+
width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
|
|
10802
|
+
height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
|
|
10803
|
+
needsAlpha: plan2.dimensions.format !== "mp4",
|
|
10804
|
+
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
10805
|
+
// The encoder writes video only; audio joins at assemble time or in the
|
|
10806
|
+
// chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
|
|
10807
|
+
// sidecar AND the mp4 audio mux.
|
|
10808
|
+
hasAudio: false,
|
|
10809
|
+
isPngSequence,
|
|
10810
|
+
// `DistributedFormat` has no "gif" member — distributed chunks are
|
|
10811
|
+
// always video segments (gif renders in-process only).
|
|
10812
|
+
isGif: false,
|
|
10813
|
+
preset,
|
|
10814
|
+
effectiveQuality,
|
|
10815
|
+
effectiveBitrate,
|
|
10816
|
+
engineConfig: {
|
|
10817
|
+
ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
|
|
10818
|
+
vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
|
|
10819
|
+
},
|
|
10820
|
+
// Distributed chunks emit a single ffmpeg call per chunk; the
|
|
10821
|
+
// in-process per-chunk-within-chunk path would re-split our
|
|
10822
|
+
// already-chunked work.
|
|
10823
|
+
enableChunkedEncode: false,
|
|
10824
|
+
chunkedEncodeSize: framesInChunk,
|
|
10825
|
+
abortSignal: void 0,
|
|
10826
|
+
assertNotAborted: () => {
|
|
10827
|
+
},
|
|
10828
|
+
// GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
|
|
10829
|
+
// first frame is an IDR keyframe and concat-copy at assemble time
|
|
10830
|
+
// round-trips losslessly.
|
|
10831
|
+
lockGopForChunkConcat: !isPngSequence,
|
|
10832
|
+
gopSize: framesInChunk
|
|
10833
|
+
};
|
|
10662
10834
|
const captureStarted = Date.now();
|
|
10663
10835
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
10664
10836
|
forceScreenshot: forceScreenshotForChunk,
|
|
@@ -10691,6 +10863,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10691
10863
|
}
|
|
10692
10864
|
const captureSession = session;
|
|
10693
10865
|
session = null;
|
|
10866
|
+
overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
|
|
10694
10867
|
await runCaptureStage({
|
|
10695
10868
|
fileServer,
|
|
10696
10869
|
workDir,
|
|
@@ -10710,7 +10883,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10710
10883
|
abortSignal: void 0,
|
|
10711
10884
|
assertNotAborted: () => {
|
|
10712
10885
|
},
|
|
10713
|
-
frameRange: range
|
|
10886
|
+
frameRange: range,
|
|
10887
|
+
onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame)
|
|
10888
|
+
}).catch(async (captureError) => {
|
|
10889
|
+
await overlap.current?.abort();
|
|
10890
|
+
overlap.current = null;
|
|
10891
|
+
throw captureError;
|
|
10714
10892
|
});
|
|
10715
10893
|
return observedChunkCaptureMode(capturePerfs);
|
|
10716
10894
|
},
|
|
@@ -10742,60 +10920,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10742
10920
|
log
|
|
10743
10921
|
);
|
|
10744
10922
|
framesEncoded = framesInChunk;
|
|
10745
|
-
const isPngSequence = plan2.dimensions.format === "png-sequence";
|
|
10746
|
-
outputKind = isPngSequence ? "frame-dir" : "file";
|
|
10747
|
-
const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
|
|
10748
|
-
const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
|
|
10749
|
-
const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
|
|
10750
|
-
const effectiveQuality = encoder.crf ?? preset.quality;
|
|
10751
|
-
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
10752
|
-
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
10753
|
-
if (isPngSequence) {
|
|
10754
|
-
if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
10755
|
-
} else {
|
|
10756
|
-
const outDir = join26(outputChunkPath, "..");
|
|
10757
|
-
if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
10758
|
-
}
|
|
10759
10923
|
const encodeStarted = Date.now();
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
outputPath: videoOnlyPath,
|
|
10764
|
-
framesDir,
|
|
10765
|
-
videoOnlyPath,
|
|
10766
|
-
width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
|
|
10767
|
-
height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
|
|
10768
|
-
needsAlpha: plan2.dimensions.format !== "mp4",
|
|
10769
|
-
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
10770
|
-
// The encoder writes video only; audio joins at assemble time or in the
|
|
10771
|
-
// chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
|
|
10772
|
-
// sidecar AND the mp4 audio mux.
|
|
10773
|
-
hasAudio: false,
|
|
10774
|
-
isPngSequence,
|
|
10775
|
-
// `DistributedFormat` has no "gif" member — distributed chunks are
|
|
10776
|
-
// always video segments (gif renders in-process only).
|
|
10777
|
-
isGif: false,
|
|
10778
|
-
preset,
|
|
10779
|
-
effectiveQuality,
|
|
10780
|
-
effectiveBitrate,
|
|
10781
|
-
engineConfig: {
|
|
10782
|
-
ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
|
|
10783
|
-
vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
|
|
10784
|
-
},
|
|
10785
|
-
// Distributed chunks emit a single ffmpeg call per chunk; the
|
|
10786
|
-
// in-process per-chunk-within-chunk path would re-split our
|
|
10787
|
-
// already-chunked work.
|
|
10788
|
-
enableChunkedEncode: false,
|
|
10789
|
-
chunkedEncodeSize: framesInChunk,
|
|
10790
|
-
abortSignal: void 0,
|
|
10791
|
-
assertNotAborted: () => {
|
|
10792
|
-
},
|
|
10793
|
-
// GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
|
|
10794
|
-
// first frame is an IDR keyframe and concat-copy at assemble time
|
|
10795
|
-
// round-trips losslessly.
|
|
10796
|
-
lockGopForChunkConcat: !isPngSequence,
|
|
10797
|
-
gopSize: framesInChunk
|
|
10798
|
-
});
|
|
10924
|
+
const overlapped = overlap.current;
|
|
10925
|
+
overlap.current = null;
|
|
10926
|
+
await (overlapped ? overlapped.finish() : runEncodeStage(encodeInput));
|
|
10799
10927
|
if (encoder.chunksCarryAudio) {
|
|
10800
10928
|
const audioScratchPath = `${outputChunkPath}.audio.m4a`;
|
|
10801
10929
|
const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
|
|
@@ -10813,6 +10941,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10813
10941
|
}
|
|
10814
10942
|
encodeStageMs = Date.now() - encodeStarted;
|
|
10815
10943
|
} finally {
|
|
10944
|
+
await overlap.current?.abort().catch(() => {
|
|
10945
|
+
});
|
|
10816
10946
|
await extraction?.catch(() => {
|
|
10817
10947
|
});
|
|
10818
10948
|
if (session) {
|
|
@@ -11242,6 +11372,24 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11242
11372
|
}
|
|
11243
11373
|
var isMissing = (result) => result.error?.code === "ENOENT";
|
|
11244
11374
|
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg8(["-version"], { signal }));
|
|
11375
|
+
|
|
11376
|
+
// src/services/distributed/probeFacts.ts
|
|
11377
|
+
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
11378
|
+
import { extname as extname6 } from "node:path";
|
|
11379
|
+
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
11380
|
+
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
11381
|
+
function wantsProbeFacts(path) {
|
|
11382
|
+
return PROBE_FACTS_EXTENSIONS.has(extname6(path).toLowerCase());
|
|
11383
|
+
}
|
|
11384
|
+
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
11385
|
+
try {
|
|
11386
|
+
writeFileSync11(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
11387
|
+
return "built";
|
|
11388
|
+
} catch (error) {
|
|
11389
|
+
if (signal?.aborted) throw error;
|
|
11390
|
+
return /ffprobe not found/i.test(String(error.message)) ? "unavailable" : "none";
|
|
11391
|
+
}
|
|
11392
|
+
}
|
|
11245
11393
|
export {
|
|
11246
11394
|
AUDIO_SIDECAR_MIN_BYTES,
|
|
11247
11395
|
CHUNK_EXCEEDS_WORKER_TIMEOUT,
|
|
@@ -11289,6 +11437,7 @@ export {
|
|
|
11289
11437
|
buildAudioSidecar,
|
|
11290
11438
|
buildChunkSlices,
|
|
11291
11439
|
buildPlanAudio,
|
|
11440
|
+
buildProbeFacts,
|
|
11292
11441
|
buildStitchedHeader,
|
|
11293
11442
|
chunksAreStitchedMp4,
|
|
11294
11443
|
createPlanV2FromExecutionPlan,
|
|
@@ -11323,5 +11472,6 @@ export {
|
|
|
11323
11472
|
validateDistributedRenderConfig,
|
|
11324
11473
|
validatePlanV2MaterializedTarget,
|
|
11325
11474
|
validateVariablesPayload,
|
|
11326
|
-
wantsAudioSidecar
|
|
11475
|
+
wantsAudioSidecar,
|
|
11476
|
+
wantsProbeFacts
|
|
11327
11477
|
};
|