@hyperframes/producer 0.8.192 → 0.8.194
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 +321 -122
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +71 -65
- package/dist/index.js +580 -456
- package/dist/public-server.js +435 -323
- package/dist/services/distributed/plan.d.ts +2 -0
- package/dist/services/distributed/probeFacts.d.ts +4 -0
- package/dist/services/htmlCompiler.d.ts +2 -1
- package/dist/services/render/artifactTransaction.d.ts +3 -1
- 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 +6 -2
- 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 {
|
|
@@ -1480,6 +1497,8 @@ import {
|
|
|
1480
1497
|
rewriteInlineStyleAssetUrls
|
|
1481
1498
|
} from "@hyperframes/core";
|
|
1482
1499
|
import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
|
|
1500
|
+
import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
1501
|
+
import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
|
|
1483
1502
|
import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
|
|
1484
1503
|
import {
|
|
1485
1504
|
assignBundledRuntimeCompositionIds,
|
|
@@ -1684,19 +1703,11 @@ import {
|
|
|
1684
1703
|
|
|
1685
1704
|
// src/services/animatedGifPrep.ts
|
|
1686
1705
|
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";
|
|
1706
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync2 } from "node:fs";
|
|
1696
1707
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve4 } from "node:path";
|
|
1697
1708
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
1698
1709
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
1699
|
-
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2 } from "@hyperframes/engine";
|
|
1710
|
+
import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
|
|
1700
1711
|
var PREPARED_GIF_SUBDIR = "_animated_gif";
|
|
1701
1712
|
var CACHE_SCHEMA = "hfgif-v1";
|
|
1702
1713
|
function splitUrlSuffix(src) {
|
|
@@ -1875,7 +1886,7 @@ async function ensurePreparedWebm(input) {
|
|
|
1875
1886
|
throw new Error("Animated GIF transcode produced an empty output");
|
|
1876
1887
|
}
|
|
1877
1888
|
if (!isUsableFile(input.cachePath)) {
|
|
1878
|
-
|
|
1889
|
+
renameWithRetry3(tmpPath, input.cachePath);
|
|
1879
1890
|
} else {
|
|
1880
1891
|
rmSync3(tmpPath, { force: true });
|
|
1881
1892
|
}
|
|
@@ -3463,6 +3474,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
3463
3474
|
const intrinsicDuration = isImage ? 0 : htmlEl.duration;
|
|
3464
3475
|
const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
|
|
3465
3476
|
const mediaStartRaw = htmlEl.getAttribute("data-media-start");
|
|
3477
|
+
const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
|
|
3478
|
+
const automationRaw = htmlEl.getAttribute("data-automation");
|
|
3479
|
+
const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
|
|
3466
3480
|
const loop = htmlEl.hasAttribute("loop");
|
|
3467
3481
|
const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
|
|
3468
3482
|
const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
|
|
@@ -3478,6 +3492,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
3478
3492
|
durationInferred,
|
|
3479
3493
|
playbackStartRaw,
|
|
3480
3494
|
mediaStartRaw,
|
|
3495
|
+
playbackRateRaw,
|
|
3496
|
+
automationRaw,
|
|
3497
|
+
defaultPlaybackRate,
|
|
3481
3498
|
loop,
|
|
3482
3499
|
hasAudio,
|
|
3483
3500
|
volume,
|
|
@@ -3487,7 +3504,17 @@ async function discoverMediaFromBrowser(page) {
|
|
|
3487
3504
|
return results;
|
|
3488
3505
|
});
|
|
3489
3506
|
return elements.map(
|
|
3490
|
-
({
|
|
3507
|
+
({
|
|
3508
|
+
endRaw,
|
|
3509
|
+
durationRaw,
|
|
3510
|
+
intrinsicDuration,
|
|
3511
|
+
playbackStartRaw,
|
|
3512
|
+
mediaStartRaw,
|
|
3513
|
+
playbackRateRaw,
|
|
3514
|
+
automationRaw,
|
|
3515
|
+
defaultPlaybackRate,
|
|
3516
|
+
...element
|
|
3517
|
+
}) => ({
|
|
3491
3518
|
...element,
|
|
3492
3519
|
end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
|
|
3493
3520
|
duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
|
|
@@ -3497,7 +3524,14 @@ async function discoverMediaFromBrowser(page) {
|
|
|
3497
3524
|
if (name === "data-media-start") return mediaStartRaw;
|
|
3498
3525
|
return null;
|
|
3499
3526
|
}
|
|
3500
|
-
})
|
|
3527
|
+
}),
|
|
3528
|
+
playbackRate: resolveRateSpec(
|
|
3529
|
+
automationRaw,
|
|
3530
|
+
readPlaybackRate(
|
|
3531
|
+
(name) => name === "data-playback-rate" ? playbackRateRaw : null,
|
|
3532
|
+
defaultPlaybackRate
|
|
3533
|
+
)
|
|
3534
|
+
)
|
|
3501
3535
|
})
|
|
3502
3536
|
);
|
|
3503
3537
|
}
|
|
@@ -3917,9 +3951,18 @@ import {
|
|
|
3917
3951
|
getCompositionDuration,
|
|
3918
3952
|
initializeSession as initializeSession4,
|
|
3919
3953
|
isTransientBrowserError,
|
|
3920
|
-
probeBeginFrameLiveness
|
|
3954
|
+
probeBeginFrameLiveness,
|
|
3955
|
+
videoAudioId
|
|
3921
3956
|
} from "@hyperframes/engine";
|
|
3922
|
-
import {
|
|
3957
|
+
import {
|
|
3958
|
+
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
3959
|
+
fpsToNumber as fpsToNumber7,
|
|
3960
|
+
normalizeRateSpec as normalizeRateSpec3,
|
|
3961
|
+
readMediaStart as readMediaStart2,
|
|
3962
|
+
shiftRateLane as shiftRateLane2,
|
|
3963
|
+
sourceTimeAt as sourceTimeAt3,
|
|
3964
|
+
timeAtSourceTime
|
|
3965
|
+
} from "@hyperframes/core";
|
|
3923
3966
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
3924
3967
|
|
|
3925
3968
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -3978,16 +4021,28 @@ function authoredMediaStarts(html) {
|
|
|
3978
4021
|
}
|
|
3979
4022
|
return starts;
|
|
3980
4023
|
}
|
|
3981
|
-
function
|
|
4024
|
+
function isVideoAudio(audio, videoId) {
|
|
4025
|
+
return audio.type === "video" && audio.id === videoAudioId(videoId);
|
|
4026
|
+
}
|
|
4027
|
+
function adoptRuntimeMediaTiming(composition, el, authored) {
|
|
3982
4028
|
const shift = el.mediaStart - authored;
|
|
3983
|
-
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
3984
4029
|
const entries = el.tagName === "video" ? [
|
|
3985
4030
|
...composition.videos.filter((video) => video.id === el.id),
|
|
3986
|
-
...composition.audios.filter(
|
|
3987
|
-
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
3988
|
-
)
|
|
4031
|
+
...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
|
|
3989
4032
|
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
3990
|
-
for (const entry of entries)
|
|
4033
|
+
for (const entry of entries) {
|
|
4034
|
+
if (typeof entry.playbackRate !== "object") {
|
|
4035
|
+
if (typeof el.playbackRate === "object") {
|
|
4036
|
+
const consumed = Math.max(0, entry.mediaStart - authored);
|
|
4037
|
+
const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
|
|
4038
|
+
entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
|
|
4039
|
+
entry.playbackRate = shiftRateLane2(el.playbackRate, head);
|
|
4040
|
+
} else {
|
|
4041
|
+
entry.playbackRate = el.playbackRate;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
|
|
4045
|
+
}
|
|
3991
4046
|
}
|
|
3992
4047
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
3993
4048
|
if (projectedEnd <= 0) return existingEnd;
|
|
@@ -4271,7 +4326,7 @@ async function runProbeStage(input) {
|
|
|
4271
4326
|
sourceChanged,
|
|
4272
4327
|
el.durationInferred
|
|
4273
4328
|
);
|
|
4274
|
-
|
|
4329
|
+
adoptRuntimeMediaTiming(
|
|
4275
4330
|
composition,
|
|
4276
4331
|
el,
|
|
4277
4332
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -4294,6 +4349,7 @@ async function runProbeStage(input) {
|
|
|
4294
4349
|
start: el.start,
|
|
4295
4350
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
4296
4351
|
mediaStart: el.mediaStart,
|
|
4352
|
+
playbackRate: el.playbackRate,
|
|
4297
4353
|
loop: el.loop,
|
|
4298
4354
|
hasAudio: el.hasAudio && !el.muted
|
|
4299
4355
|
});
|
|
@@ -4318,7 +4374,7 @@ async function runProbeStage(input) {
|
|
|
4318
4374
|
sourceChanged,
|
|
4319
4375
|
el.durationInferred
|
|
4320
4376
|
);
|
|
4321
|
-
|
|
4377
|
+
adoptRuntimeMediaTiming(
|
|
4322
4378
|
composition,
|
|
4323
4379
|
el,
|
|
4324
4380
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -4336,6 +4392,7 @@ async function runProbeStage(input) {
|
|
|
4336
4392
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
4337
4393
|
mediaStart: el.mediaStart,
|
|
4338
4394
|
loop: el.loop,
|
|
4395
|
+
playbackRate: el.playbackRate,
|
|
4339
4396
|
layer: 0,
|
|
4340
4397
|
volume: el.volume,
|
|
4341
4398
|
type: "audio"
|
|
@@ -4596,7 +4653,8 @@ import {
|
|
|
4596
4653
|
isHttpUrl as isHttpUrl2,
|
|
4597
4654
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
4598
4655
|
runVideoExtractionWithRetry,
|
|
4599
|
-
safeVideoExtractionSourceIdentity
|
|
4656
|
+
safeVideoExtractionSourceIdentity,
|
|
4657
|
+
videoAudioId as videoAudioId2
|
|
4600
4658
|
} from "@hyperframes/engine";
|
|
4601
4659
|
|
|
4602
4660
|
// src/services/render/extractionFailureMetadata.ts
|
|
@@ -4954,7 +5012,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
4954
5012
|
const video = composition.videos.find((v) => v.id === ext.videoId);
|
|
4955
5013
|
if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
|
|
4956
5014
|
composition.audios.push({
|
|
4957
|
-
id:
|
|
5015
|
+
id: videoAudioId2(video.id),
|
|
4958
5016
|
src: video.src,
|
|
4959
5017
|
start: video.start,
|
|
4960
5018
|
end: video.end,
|
|
@@ -5007,7 +5065,7 @@ import {
|
|
|
5007
5065
|
|
|
5008
5066
|
// src/services/render/audioPadTrim.ts
|
|
5009
5067
|
import { spawn } from "node:child_process";
|
|
5010
|
-
import { mkdtempSync as mkdtempSync2,
|
|
5068
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
|
|
5011
5069
|
import { dirname as dirname5, join as join9 } from "node:path";
|
|
5012
5070
|
import {
|
|
5013
5071
|
DEFAULT_CONFIG,
|
|
@@ -5016,6 +5074,7 @@ import {
|
|
|
5016
5074
|
extractAudioMetadata as extractAudioMetadata3,
|
|
5017
5075
|
getFfprobeBinary,
|
|
5018
5076
|
ManagedChildProcess,
|
|
5077
|
+
renameWithRetry as renameWithRetry4,
|
|
5019
5078
|
runFfmpeg as runFfmpeg4,
|
|
5020
5079
|
trackChildProcess
|
|
5021
5080
|
} from "@hyperframes/engine";
|
|
@@ -5271,7 +5330,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5271
5330
|
}
|
|
5272
5331
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
5273
5332
|
if (measuredPath !== correctedPath) return { success: true };
|
|
5274
|
-
|
|
5333
|
+
renameWithRetry4(correctedPath, input.audioPath);
|
|
5275
5334
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
5276
5335
|
}
|
|
5277
5336
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -5461,6 +5520,7 @@ async function runAudioStage(input) {
|
|
|
5461
5520
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
5462
5521
|
let audioLoweredDb;
|
|
5463
5522
|
if (result.hasAudio) {
|
|
5523
|
+
const guardStart = Date.now();
|
|
5464
5524
|
const peak = await constrainAacTruePeak({
|
|
5465
5525
|
audioPath: result.audioOutputPath,
|
|
5466
5526
|
sourcePath: pcmSourcePath,
|
|
@@ -5470,6 +5530,7 @@ async function runAudioStage(input) {
|
|
|
5470
5530
|
});
|
|
5471
5531
|
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
5472
5532
|
audioLoweredDb = peak.loweredDb;
|
|
5533
|
+
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
5473
5534
|
}
|
|
5474
5535
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
5475
5536
|
} finally {
|
|
@@ -5495,6 +5556,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5495
5556
|
let hasAudio = false;
|
|
5496
5557
|
let audioError;
|
|
5497
5558
|
let audioFailures;
|
|
5559
|
+
let audioPhaseMs;
|
|
5498
5560
|
if (audios.length > 0) {
|
|
5499
5561
|
let audioResult;
|
|
5500
5562
|
try {
|
|
@@ -5544,6 +5606,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5544
5606
|
assertAudioNotInterrupted(audioResult.failures);
|
|
5545
5607
|
hasAudio = audioResult.success;
|
|
5546
5608
|
audioFailures = audioResult.failures;
|
|
5609
|
+
audioPhaseMs = audioResult.phaseMs;
|
|
5547
5610
|
if (!hasAudio) {
|
|
5548
5611
|
audioError = audioResult.error ?? "audio mix failed for an unknown reason";
|
|
5549
5612
|
for (const failure of audioResult.failures ?? []) {
|
|
@@ -5562,7 +5625,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5562
5625
|
audioOutputPath,
|
|
5563
5626
|
hasAudio,
|
|
5564
5627
|
audioError,
|
|
5565
|
-
audioFailures
|
|
5628
|
+
audioFailures,
|
|
5629
|
+
audioPhaseMs
|
|
5566
5630
|
};
|
|
5567
5631
|
}
|
|
5568
5632
|
|
|
@@ -5672,6 +5736,7 @@ async function runCaptureStage(input) {
|
|
|
5672
5736
|
let { workerCount } = plan2;
|
|
5673
5737
|
const { forceScreenshot } = plan2;
|
|
5674
5738
|
let lastBrowserConsole = [];
|
|
5739
|
+
const workerPageErrors = [];
|
|
5675
5740
|
let captureBeyondViewport = probeSession?.options.captureBeyondViewport;
|
|
5676
5741
|
const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
|
|
5677
5742
|
if (frameRange !== void 0) {
|
|
@@ -5709,6 +5774,7 @@ async function runCaptureStage(input) {
|
|
|
5709
5774
|
abortSignal,
|
|
5710
5775
|
frameRangeStart: frameRange?.startFrame,
|
|
5711
5776
|
dedupPerfs,
|
|
5777
|
+
pageErrorLines: workerPageErrors,
|
|
5712
5778
|
onProgress: (progress) => {
|
|
5713
5779
|
if (progress.latestWorkerPhase) {
|
|
5714
5780
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
@@ -5729,6 +5795,11 @@ async function runCaptureStage(input) {
|
|
|
5729
5795
|
},
|
|
5730
5796
|
cfg: captureCfg,
|
|
5731
5797
|
log
|
|
5798
|
+
}).catch((error) => {
|
|
5799
|
+
throw wrapCaptureStageError(
|
|
5800
|
+
error,
|
|
5801
|
+
withWorkerPageErrors(probeSession?.browserConsoleBuffer ?? [], workerPageErrors)
|
|
5802
|
+
);
|
|
5732
5803
|
});
|
|
5733
5804
|
captureAttempts.push(...attempts);
|
|
5734
5805
|
const lastAttempt = attempts[attempts.length - 1];
|
|
@@ -5741,6 +5812,7 @@ async function runCaptureStage(input) {
|
|
|
5741
5812
|
await closeCaptureSession5(probeSession);
|
|
5742
5813
|
probeSession = null;
|
|
5743
5814
|
}
|
|
5815
|
+
lastBrowserConsole = withWorkerPageErrors(lastBrowserConsole, workerPageErrors);
|
|
5744
5816
|
} else {
|
|
5745
5817
|
const sequential = await runSequentialDiskCapture(
|
|
5746
5818
|
input,
|
|
@@ -5948,6 +6020,7 @@ import {
|
|
|
5948
6020
|
statSync as statSync3,
|
|
5949
6021
|
writeFileSync as writeFileSync2
|
|
5950
6022
|
} from "node:fs";
|
|
6023
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
5951
6024
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
5952
6025
|
import {
|
|
5953
6026
|
encodeFramesChunkedConcat,
|
|
@@ -6117,6 +6190,100 @@ function videoEncoderOptions(input, engineCfg) {
|
|
|
6117
6190
|
gopSize: input.gopSize
|
|
6118
6191
|
};
|
|
6119
6192
|
}
|
|
6193
|
+
function canEncodeWhileCapturing(input, workerCount, cpuCount) {
|
|
6194
|
+
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";
|
|
6195
|
+
}
|
|
6196
|
+
async function readLandedFrame(workerPath, mergedPath) {
|
|
6197
|
+
if (workerPath !== void 0) {
|
|
6198
|
+
try {
|
|
6199
|
+
return await readFile2(workerPath);
|
|
6200
|
+
} catch (error) {
|
|
6201
|
+
if (error.code !== "ENOENT") throw error;
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
return readFile2(mergedPath);
|
|
6205
|
+
}
|
|
6206
|
+
function encodeWhileCapturing(input, totalFrames) {
|
|
6207
|
+
const engineCfg = resolveEngineConfig(input);
|
|
6208
|
+
const stop = new AbortController();
|
|
6209
|
+
const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, stop.signal]) : stop.signal;
|
|
6210
|
+
const landed = /* @__PURE__ */ new Map();
|
|
6211
|
+
let captureDone = false;
|
|
6212
|
+
let framesFed = 0;
|
|
6213
|
+
let wake = null;
|
|
6214
|
+
const nudge = () => {
|
|
6215
|
+
wake?.();
|
|
6216
|
+
wake = null;
|
|
6217
|
+
};
|
|
6218
|
+
signal.addEventListener("abort", nudge, { once: true });
|
|
6219
|
+
let reportFed = null;
|
|
6220
|
+
const encoder = spawnStreamingEncoder4(
|
|
6221
|
+
input.videoOnlyPath,
|
|
6222
|
+
{ ...videoEncoderOptions(input, engineCfg), imageFormat: "jpeg" },
|
|
6223
|
+
signal,
|
|
6224
|
+
engineCfg
|
|
6225
|
+
);
|
|
6226
|
+
const fed = (async () => {
|
|
6227
|
+
const stream = await encoder;
|
|
6228
|
+
for (let next = 0; next < totalFrames; next++) {
|
|
6229
|
+
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
6230
|
+
await new Promise((resolve9) => wake = resolve9);
|
|
6231
|
+
}
|
|
6232
|
+
if (signal.aborted) return;
|
|
6233
|
+
const mergedPath = join12(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
6234
|
+
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
6235
|
+
landed.delete(next);
|
|
6236
|
+
if (!await stream.writeFrame(frame)) {
|
|
6237
|
+
throw new Error(stream.getExitError() ?? "encoder exited before the last frame");
|
|
6238
|
+
}
|
|
6239
|
+
framesFed++;
|
|
6240
|
+
reportFed?.(framesFed);
|
|
6241
|
+
}
|
|
6242
|
+
})().then(
|
|
6243
|
+
() => void 0,
|
|
6244
|
+
(error) => error ?? new Error("encode failed")
|
|
6245
|
+
);
|
|
6246
|
+
const closeEncoder = async () => (await encoder.catch(() => null))?.close();
|
|
6247
|
+
return {
|
|
6248
|
+
onFrameLanded: (frame) => {
|
|
6249
|
+
landed.set(frame.index, frame.path);
|
|
6250
|
+
nudge();
|
|
6251
|
+
},
|
|
6252
|
+
finish: async () => {
|
|
6253
|
+
const start = Date.now();
|
|
6254
|
+
captureDone = true;
|
|
6255
|
+
input.log.info("[Render] Encoded frames while capturing", {
|
|
6256
|
+
framesFed,
|
|
6257
|
+
totalFrames
|
|
6258
|
+
});
|
|
6259
|
+
const encoded = startEncodeProgress(input.job, "Encoding video", input.onProgress);
|
|
6260
|
+
reportFed = encoded;
|
|
6261
|
+
nudge();
|
|
6262
|
+
const failure = await fed;
|
|
6263
|
+
const result = await closeEncoder();
|
|
6264
|
+
input.assertNotAborted();
|
|
6265
|
+
if (failure === void 0 && result?.success) {
|
|
6266
|
+
encoded();
|
|
6267
|
+
return { encodeMs: Date.now() - start };
|
|
6268
|
+
}
|
|
6269
|
+
input.log.warn(
|
|
6270
|
+
"[Render] Encoding while capturing failed; encoding the frames after capture",
|
|
6271
|
+
{
|
|
6272
|
+
error: String(failure ?? result?.error)
|
|
6273
|
+
}
|
|
6274
|
+
);
|
|
6275
|
+
return runEncodeStage(input);
|
|
6276
|
+
},
|
|
6277
|
+
abort: async () => {
|
|
6278
|
+
stop.abort();
|
|
6279
|
+
await fed;
|
|
6280
|
+
await closeEncoder();
|
|
6281
|
+
},
|
|
6282
|
+
get framesFed() {
|
|
6283
|
+
return framesFed;
|
|
6284
|
+
}
|
|
6285
|
+
};
|
|
6286
|
+
}
|
|
6120
6287
|
async function runEncodeStage(input) {
|
|
6121
6288
|
const {
|
|
6122
6289
|
job,
|
|
@@ -6430,6 +6597,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6430
6597
|
options.cfg
|
|
6431
6598
|
);
|
|
6432
6599
|
pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
|
|
6600
|
+
if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
|
|
6433
6601
|
} finally {
|
|
6434
6602
|
await mergeWorkerFrames(attemptWorkDir, tasks, options.framesDir);
|
|
6435
6603
|
}
|
|
@@ -7508,15 +7676,9 @@ import { join as join19 } from "node:path";
|
|
|
7508
7676
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
|
|
7509
7677
|
|
|
7510
7678
|
// 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";
|
|
7679
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
|
|
7519
7680
|
import { open } from "node:fs/promises";
|
|
7681
|
+
import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
|
|
7520
7682
|
import { dirname as dirname9, join as join18 } from "node:path";
|
|
7521
7683
|
import { finished, pipeline } from "node:stream/promises";
|
|
7522
7684
|
function boxAt(src, off, at, limit) {
|
|
@@ -7889,7 +8051,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
7889
8051
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
7890
8052
|
out.end();
|
|
7891
8053
|
await finished(out);
|
|
7892
|
-
|
|
8054
|
+
renameWithRetry5(join18(workDir, "out.mp4"), outputPath);
|
|
7893
8055
|
} finally {
|
|
7894
8056
|
out.destroy();
|
|
7895
8057
|
rmSync8(workDir, { recursive: true, force: true });
|
|
@@ -8619,7 +8781,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
8619
8781
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
8620
8782
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
8621
8783
|
const remoteLocalPath = (rel) => join21(projectDir, ...rel.split("/"));
|
|
8622
|
-
for (const [, file] of remoteFiles)
|
|
8784
|
+
for (const [, file] of remoteFiles) {
|
|
8785
|
+
declareContentAddressedUrl(file.url, file.sha256);
|
|
8786
|
+
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
8787
|
+
}
|
|
8623
8788
|
const urlByAbsentLocalPath = new Map(
|
|
8624
8789
|
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
8625
8790
|
);
|
|
@@ -8741,7 +8906,8 @@ function mixPlanAudio(c, compiledDir) {
|
|
|
8741
8906
|
audios: c.composition.audios,
|
|
8742
8907
|
masterFxChain: c.composition.masterFxChain,
|
|
8743
8908
|
abortSignal: c.abortSignal,
|
|
8744
|
-
assertNotAborted: c.assertNotAborted
|
|
8909
|
+
assertNotAborted: c.assertNotAborted,
|
|
8910
|
+
log: c.log
|
|
8745
8911
|
});
|
|
8746
8912
|
}
|
|
8747
8913
|
async function extractPlanVideos(c, deferRangeExtraction) {
|
|
@@ -8760,6 +8926,7 @@ async function extractPlanVideos(c, deferRangeExtraction) {
|
|
|
8760
8926
|
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
8761
8927
|
if (extractResult.extractionResult) {
|
|
8762
8928
|
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
8929
|
+
c.log.info("[plan] extract phases", { ...extractResult.extractionResult.phaseBreakdown });
|
|
8763
8930
|
}
|
|
8764
8931
|
return extractResult;
|
|
8765
8932
|
}
|
|
@@ -8805,12 +8972,12 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8805
8972
|
const videoSourcesDir = join21(planDir, "video-sources");
|
|
8806
8973
|
rmSync9(videoSourcesDir, { recursive: true, force: true });
|
|
8807
8974
|
if (existsSync15(stagedVideoFrames)) {
|
|
8808
|
-
|
|
8975
|
+
renameWithRetry6(stagedVideoFrames, videoFramesDst);
|
|
8809
8976
|
} else {
|
|
8810
8977
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
8811
8978
|
}
|
|
8812
8979
|
if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
|
|
8813
|
-
|
|
8980
|
+
renameWithRetry6(compiledDir, finalCompiledDir);
|
|
8814
8981
|
const shipVideoSource = createVideoSourceShipper({
|
|
8815
8982
|
planDir,
|
|
8816
8983
|
projectDir,
|
|
@@ -8913,7 +9080,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8913
9080
|
}
|
|
8914
9081
|
const planAudioPath = join21(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
8915
9082
|
if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
|
|
8916
|
-
|
|
9083
|
+
renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
|
|
8917
9084
|
}
|
|
8918
9085
|
if (chunksCarryAudio && existsSync15(planAudioPath)) {
|
|
8919
9086
|
if (options.sliceChunkAudio) {
|
|
@@ -9027,7 +9194,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
9027
9194
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
9028
9195
|
}
|
|
9029
9196
|
if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
|
|
9030
|
-
|
|
9197
|
+
renameWithRetry6(mixed.audioOutputPath, outputPath);
|
|
9031
9198
|
return { hasAudio: true };
|
|
9032
9199
|
} finally {
|
|
9033
9200
|
rmSync9(c.workDir, { recursive: true, force: true });
|
|
@@ -9059,7 +9226,6 @@ import {
|
|
|
9059
9226
|
mkdtempSync as mkdtempSync5,
|
|
9060
9227
|
readFileSync as readFileSync10,
|
|
9061
9228
|
readdirSync as readdirSync7,
|
|
9062
|
-
renameSync as renameSync6,
|
|
9063
9229
|
rmSync as rmSync11,
|
|
9064
9230
|
statSync as statSync6,
|
|
9065
9231
|
writeFileSync as writeFileSync8
|
|
@@ -9067,7 +9233,7 @@ import {
|
|
|
9067
9233
|
import { tmpdir } from "node:os";
|
|
9068
9234
|
import { dirname as dirname11, isAbsolute as isAbsolute4, join as join24, relative as relative6, resolve as resolve8, sep as sep3 } from "node:path";
|
|
9069
9235
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
9070
|
-
import { createFrameLookupTable as createFrameLookupTable4 } from "@hyperframes/engine";
|
|
9236
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
9071
9237
|
|
|
9072
9238
|
// src/services/distributed/planV2Publisher.ts
|
|
9073
9239
|
import {
|
|
@@ -9076,11 +9242,11 @@ import {
|
|
|
9076
9242
|
linkSync as linkSync2,
|
|
9077
9243
|
mkdirSync as mkdirSync11,
|
|
9078
9244
|
mkdtempSync as mkdtempSync4,
|
|
9079
|
-
renameSync as renameSync5,
|
|
9080
9245
|
rmSync as rmSync10,
|
|
9081
9246
|
statSync as statSync5,
|
|
9082
9247
|
writeFileSync as writeFileSync7
|
|
9083
9248
|
} from "node:fs";
|
|
9249
|
+
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
9084
9250
|
import { dirname as dirname10, join as join23 } from "node:path";
|
|
9085
9251
|
|
|
9086
9252
|
// src/services/distributed/planV2Errors.ts
|
|
@@ -9169,7 +9335,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9169
9335
|
if (!canFallbackToCopy(error)) throw error;
|
|
9170
9336
|
copyFileSync6(blob.sourcePath, temporaryPath);
|
|
9171
9337
|
}
|
|
9172
|
-
|
|
9338
|
+
renameWithRetry7(temporaryPath, destinationPath);
|
|
9173
9339
|
} finally {
|
|
9174
9340
|
rmSync10(stagingDir, { recursive: true, force: true });
|
|
9175
9341
|
}
|
|
@@ -9183,7 +9349,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9183
9349
|
}
|
|
9184
9350
|
}
|
|
9185
9351
|
writeFileSync7(join23(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
9186
|
-
|
|
9352
|
+
renameWithRetry7(this.temporaryDir, this.destinationDir);
|
|
9187
9353
|
this.#committed = true;
|
|
9188
9354
|
}
|
|
9189
9355
|
async abort() {
|
|
@@ -9450,7 +9616,7 @@ function writeBlob(sourcePath, destinationPath) {
|
|
|
9450
9616
|
const temporaryPath = join24(temporaryDir, "blob");
|
|
9451
9617
|
try {
|
|
9452
9618
|
copyFileSync7(sourcePath, temporaryPath);
|
|
9453
|
-
|
|
9619
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
9454
9620
|
} finally {
|
|
9455
9621
|
rmSync11(temporaryDir, { recursive: true, force: true });
|
|
9456
9622
|
}
|
|
@@ -9582,7 +9748,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
9582
9748
|
canonicalJsonStringify(publication.manifest),
|
|
9583
9749
|
"utf-8"
|
|
9584
9750
|
);
|
|
9585
|
-
|
|
9751
|
+
renameWithRetry8(tempDir, planV2Dir);
|
|
9586
9752
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
9587
9753
|
} catch (error) {
|
|
9588
9754
|
rmSync11(tempDir, { recursive: true, force: true });
|
|
@@ -9867,7 +10033,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
9867
10033
|
}),
|
|
9868
10034
|
"utf-8"
|
|
9869
10035
|
);
|
|
9870
|
-
|
|
10036
|
+
renameWithRetry8(tempDir, destinationDir);
|
|
9871
10037
|
} catch (error) {
|
|
9872
10038
|
rmSync11(tempDir, { recursive: true, force: true });
|
|
9873
10039
|
throw error;
|
|
@@ -9945,7 +10111,6 @@ import {
|
|
|
9945
10111
|
mkdtempSync as mkdtempSync6,
|
|
9946
10112
|
readFileSync as readFileSync11,
|
|
9947
10113
|
readdirSync as readdirSync8,
|
|
9948
|
-
renameSync as renameSync7,
|
|
9949
10114
|
rmSync as rmSync12,
|
|
9950
10115
|
statSync as statSync7,
|
|
9951
10116
|
writeFileSync as writeFileSync9
|
|
@@ -9959,7 +10124,8 @@ import {
|
|
|
9959
10124
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
9960
10125
|
resolveConfig as resolveConfig4,
|
|
9961
10126
|
runFfmpeg as runFfmpeg7,
|
|
9962
|
-
appendSoftwareEncoderThreadsArg
|
|
10127
|
+
appendSoftwareEncoderThreadsArg,
|
|
10128
|
+
renameWithRetry as renameWithRetry9
|
|
9963
10129
|
} from "@hyperframes/engine";
|
|
9964
10130
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
9965
10131
|
function cfrRateControlArgs(locked) {
|
|
@@ -10016,7 +10182,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10016
10182
|
const finalPath = join25(workDir, `output.${format}`);
|
|
10017
10183
|
const audio = audioPath !== null && existsSync18(audioPath) ? audioPath : null;
|
|
10018
10184
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
10019
|
-
|
|
10185
|
+
renameWithRetry9(finalPath, outputPath);
|
|
10020
10186
|
log.info("[assemble] stage timings", {
|
|
10021
10187
|
...stageMs,
|
|
10022
10188
|
chunks: chunkPaths.length,
|
|
@@ -10268,6 +10434,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10268
10434
|
// src/services/distributed/renderChunk.ts
|
|
10269
10435
|
import { randomBytes } from "node:crypto";
|
|
10270
10436
|
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync12, readdirSync as readdirSync9, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10437
|
+
import { cpus } from "node:os";
|
|
10271
10438
|
import { join as join26 } from "node:path";
|
|
10272
10439
|
import {
|
|
10273
10440
|
assertSwiftShader,
|
|
@@ -10572,6 +10739,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10572
10739
|
let forceScreenshotForChunk = encoder.forceScreenshot;
|
|
10573
10740
|
let shownBoxes;
|
|
10574
10741
|
let extraction;
|
|
10742
|
+
const overlap = { current: null };
|
|
10575
10743
|
try {
|
|
10576
10744
|
if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
|
|
10577
10745
|
const bootStarted = Date.now();
|
|
@@ -10659,6 +10827,59 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10659
10827
|
videoFrameLookup,
|
|
10660
10828
|
frameCacheOptions(cfg, chunkWorkerCount)
|
|
10661
10829
|
);
|
|
10830
|
+
const isPngSequence = plan2.dimensions.format === "png-sequence";
|
|
10831
|
+
outputKind = isPngSequence ? "frame-dir" : "file";
|
|
10832
|
+
const presetFormat = isPngSequence ? "mp4" : plan2.dimensions.format;
|
|
10833
|
+
const basePreset = getEncoderPreset6(job.config.quality, presetFormat, void 0);
|
|
10834
|
+
const preset = resolvePresetForLockedEncoder(basePreset, encoder.encoder);
|
|
10835
|
+
const effectiveQuality = encoder.crf ?? preset.quality;
|
|
10836
|
+
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
10837
|
+
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
10838
|
+
if (isPngSequence) {
|
|
10839
|
+
if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
10840
|
+
} else {
|
|
10841
|
+
const outDir = join26(outputChunkPath, "..");
|
|
10842
|
+
if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
10843
|
+
}
|
|
10844
|
+
const encodeInput = {
|
|
10845
|
+
job,
|
|
10846
|
+
log,
|
|
10847
|
+
outputPath: videoOnlyPath,
|
|
10848
|
+
framesDir,
|
|
10849
|
+
videoOnlyPath,
|
|
10850
|
+
width: Math.round(plan2.dimensions.width * encoder.deviceScaleFactor),
|
|
10851
|
+
height: Math.round(plan2.dimensions.height * encoder.deviceScaleFactor),
|
|
10852
|
+
needsAlpha: plan2.dimensions.format !== "mp4",
|
|
10853
|
+
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
10854
|
+
// The encoder writes video only; audio joins at assemble time or in the
|
|
10855
|
+
// chunk that carries audio. Suppressing `hasAudio` skips the png-sequence audio
|
|
10856
|
+
// sidecar AND the mp4 audio mux.
|
|
10857
|
+
hasAudio: false,
|
|
10858
|
+
isPngSequence,
|
|
10859
|
+
// `DistributedFormat` has no "gif" member — distributed chunks are
|
|
10860
|
+
// always video segments (gif renders in-process only).
|
|
10861
|
+
isGif: false,
|
|
10862
|
+
preset,
|
|
10863
|
+
effectiveQuality,
|
|
10864
|
+
effectiveBitrate,
|
|
10865
|
+
engineConfig: {
|
|
10866
|
+
ffmpegEncodeTimeout: cfg.ffmpegEncodeTimeout,
|
|
10867
|
+
vp9CpuUsed: resolveLockedVp9CpuUsed(encoder) ?? cfg.vp9CpuUsed
|
|
10868
|
+
},
|
|
10869
|
+
// Distributed chunks emit a single ffmpeg call per chunk; the
|
|
10870
|
+
// in-process per-chunk-within-chunk path would re-split our
|
|
10871
|
+
// already-chunked work.
|
|
10872
|
+
enableChunkedEncode: false,
|
|
10873
|
+
chunkedEncodeSize: framesInChunk,
|
|
10874
|
+
abortSignal: void 0,
|
|
10875
|
+
assertNotAborted: () => {
|
|
10876
|
+
},
|
|
10877
|
+
// GOP === framesInChunk + force-keyframe at frame 0 → the chunk's
|
|
10878
|
+
// first frame is an IDR keyframe and concat-copy at assemble time
|
|
10879
|
+
// round-trips losslessly.
|
|
10880
|
+
lockGopForChunkConcat: !isPngSequence,
|
|
10881
|
+
gopSize: framesInChunk
|
|
10882
|
+
};
|
|
10662
10883
|
const captureStarted = Date.now();
|
|
10663
10884
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
10664
10885
|
forceScreenshot: forceScreenshotForChunk,
|
|
@@ -10691,6 +10912,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10691
10912
|
}
|
|
10692
10913
|
const captureSession = session;
|
|
10693
10914
|
session = null;
|
|
10915
|
+
overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
|
|
10694
10916
|
await runCaptureStage({
|
|
10695
10917
|
fileServer,
|
|
10696
10918
|
workDir,
|
|
@@ -10710,7 +10932,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10710
10932
|
abortSignal: void 0,
|
|
10711
10933
|
assertNotAborted: () => {
|
|
10712
10934
|
},
|
|
10713
|
-
frameRange: range
|
|
10935
|
+
frameRange: range,
|
|
10936
|
+
onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame)
|
|
10937
|
+
}).catch(async (captureError) => {
|
|
10938
|
+
await overlap.current?.abort();
|
|
10939
|
+
overlap.current = null;
|
|
10940
|
+
throw captureError;
|
|
10714
10941
|
});
|
|
10715
10942
|
return observedChunkCaptureMode(capturePerfs);
|
|
10716
10943
|
},
|
|
@@ -10742,60 +10969,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10742
10969
|
log
|
|
10743
10970
|
);
|
|
10744
10971
|
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
10972
|
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
|
-
});
|
|
10973
|
+
const overlapped = overlap.current;
|
|
10974
|
+
overlap.current = null;
|
|
10975
|
+
await (overlapped ? overlapped.finish() : runEncodeStage(encodeInput));
|
|
10799
10976
|
if (encoder.chunksCarryAudio) {
|
|
10800
10977
|
const audioScratchPath = `${outputChunkPath}.audio.m4a`;
|
|
10801
10978
|
const audioChunks = options?.frameRange ? chunksWithSubRange(chunks, chunkIndex, range) : { chunks, index: chunkIndex };
|
|
@@ -10813,6 +10990,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10813
10990
|
}
|
|
10814
10991
|
encodeStageMs = Date.now() - encodeStarted;
|
|
10815
10992
|
} finally {
|
|
10993
|
+
await overlap.current?.abort().catch(() => {
|
|
10994
|
+
});
|
|
10816
10995
|
await extraction?.catch(() => {
|
|
10817
10996
|
});
|
|
10818
10997
|
if (session) {
|
|
@@ -11242,6 +11421,24 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11242
11421
|
}
|
|
11243
11422
|
var isMissing = (result) => result.error?.code === "ENOENT";
|
|
11244
11423
|
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg8(["-version"], { signal }));
|
|
11424
|
+
|
|
11425
|
+
// src/services/distributed/probeFacts.ts
|
|
11426
|
+
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
11427
|
+
import { extname as extname6 } from "node:path";
|
|
11428
|
+
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
11429
|
+
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
11430
|
+
function wantsProbeFacts(path) {
|
|
11431
|
+
return PROBE_FACTS_EXTENSIONS.has(extname6(path).toLowerCase());
|
|
11432
|
+
}
|
|
11433
|
+
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
11434
|
+
try {
|
|
11435
|
+
writeFileSync11(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
11436
|
+
return "built";
|
|
11437
|
+
} catch (error) {
|
|
11438
|
+
if (signal?.aborted) throw error;
|
|
11439
|
+
return /ffprobe not found/i.test(String(error.message)) ? "unavailable" : "none";
|
|
11440
|
+
}
|
|
11441
|
+
}
|
|
11245
11442
|
export {
|
|
11246
11443
|
AUDIO_SIDECAR_MIN_BYTES,
|
|
11247
11444
|
CHUNK_EXCEEDS_WORKER_TIMEOUT,
|
|
@@ -11289,6 +11486,7 @@ export {
|
|
|
11289
11486
|
buildAudioSidecar,
|
|
11290
11487
|
buildChunkSlices,
|
|
11291
11488
|
buildPlanAudio,
|
|
11489
|
+
buildProbeFacts,
|
|
11292
11490
|
buildStitchedHeader,
|
|
11293
11491
|
chunksAreStitchedMp4,
|
|
11294
11492
|
createPlanV2FromExecutionPlan,
|
|
@@ -11323,5 +11521,6 @@ export {
|
|
|
11323
11521
|
validateDistributedRenderConfig,
|
|
11324
11522
|
validatePlanV2MaterializedTarget,
|
|
11325
11523
|
validateVariablesPayload,
|
|
11326
|
-
wantsAudioSidecar
|
|
11524
|
+
wantsAudioSidecar,
|
|
11525
|
+
wantsProbeFacts
|
|
11327
11526
|
};
|