hyperframes 0.8.177 → 0.8.179
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/{add-YVP5YBNV.js → add-2CCBND75.js} +12 -12
- package/dist/{animatedGifPrep-6SPYXSHE.js → animatedGifPrep-S6XXOOXK.js} +7 -7
- package/dist/api.js +16 -16
- package/dist/{asset-CZHAZMDR.js → asset-7HOLVSD5.js} +2 -2
- package/dist/{assetCataloger-5GGNQC3Y.js → assetCataloger-MSAMR77T.js} +7 -7
- package/dist/{audio-F37HJPGZ.js → audio-356GVOLJ.js} +13 -13
- package/dist/{auth-CADZEUXD.js → auth-YHI5VR4X.js} +1 -1
- package/dist/{autoUpdate-46XNDX2V.js → autoUpdate-OP345EBV.js} +2 -2
- package/dist/{backgroundChecks-UWDSDJHJ.js → backgroundChecks-6NVELE25.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-KXXTVNPB.js → batchRender-OOQYGH5A.js} +4 -4
- package/dist/{beats-COVC2QGG.js → beats-DQH5SOHM.js} +5 -5
- package/dist/{benchmark-HDPPG2FE.js → benchmark-2W2NPNLJ.js} +9 -9
- package/dist/{browser-JEIXZKJN.js → browser-SCM75524.js} +9 -8
- package/dist/{bundleWithLocalizedFonts-PMRGOQWD.js → bundleWithLocalizedFonts-4F6HKLMZ.js} +2 -2
- package/dist/{capture-QVW6JPTF.js → capture-YRS6SZ6B.js} +11 -11
- package/dist/{capture-YMF7J55W.js → capture-ZBGAJYVH.js} +2 -2
- package/dist/{captureCompositionFrame-QUYL3DEX.js → captureCompositionFrame-Z33NWUED.js} +11 -11
- package/dist/{catalog-DTB6S66P.js → catalog-MOSGL65T.js} +13 -13
- package/dist/{catch-up-Y4KO7XHK.js → catch-up-TCAZFTMO.js} +5 -5
- package/dist/{check-3YLTRK6W.js → check-QVXSTHZB.js} +39 -39
- package/dist/{checkBrowser-LEUOWFNZ.js → checkBrowser-NF2CMWYR.js} +35 -35
- package/dist/chunk-2R5ZKWSV.js +26 -0
- package/dist/{chunk-HIP62XAW.js → chunk-2X4PQEYM.js} +1 -3
- package/dist/{chunk-42T7OP4O.js → chunk-32GKQ73S.js} +1 -1
- package/dist/{chunk-PIH4P2G6.js → chunk-3EJYUZHW.js} +16 -14
- package/dist/{chunk-L6XT6GSV.js → chunk-52L2L46Q.js} +1 -1
- package/dist/chunk-537IYAEE.js +80 -0
- package/dist/{chunk-7L4JOVV7.js → chunk-5HZAIPHY.js} +1 -1
- package/dist/{chunk-GVIH4ZQY.js → chunk-5RS2FIZU.js} +1 -1
- package/dist/{chunk-T76PLPD6.js → chunk-634HSJ3J.js} +2 -1
- package/dist/{chunk-L3LJVYXS.js → chunk-A2BB2XQU.js} +2 -2
- package/dist/{chunk-DQY6WTF7.js → chunk-A3RUVTGB.js} +1 -1
- package/dist/{chunk-P3MMF2JQ.js → chunk-AEWZDKFS.js} +1 -1
- package/dist/{chunk-OYEDHVSI.js → chunk-AFKVI7FJ.js} +1 -1
- package/dist/{chunk-DNZMBQTH.js → chunk-AHPGAESX.js} +1 -1
- package/dist/{chunk-VEQ7DKPG.js → chunk-AHS5QYKF.js} +1 -1
- package/dist/{chunk-FFDC5OWV.js → chunk-ALVXFXUG.js} +5 -3
- package/dist/{chunk-TNDNFZOK.js → chunk-BGKZ37BC.js} +2 -2
- package/dist/{chunk-LY2GLSOS.js → chunk-BJJ3QF7T.js} +1 -1
- package/dist/{chunk-GZG3ZZOO.js → chunk-BNGRVR3Y.js} +4 -4
- package/dist/{chunk-AFSBPASX.js → chunk-BW2UQR4A.js} +5 -5
- package/dist/{chunk-2PNLYDGZ.js → chunk-C5XU53VP.js} +10 -10
- package/dist/{chunk-NL5F3AD4.js → chunk-CML25PAF.js} +2 -4
- package/dist/{chunk-S3T3TDAB.js → chunk-COK5OUBX.js} +2 -2
- package/dist/{chunk-MG66UO4N.js → chunk-CW3VIQMZ.js} +1 -1
- package/dist/{chunk-6GMZRHYD.js → chunk-DHKYJYOY.js} +3 -3
- package/dist/{chunk-MPNVXG3J.js → chunk-DLF5KIBL.js} +2 -2
- package/dist/{chunk-XRKMITSS.js → chunk-E24HSMZV.js} +12 -51
- package/dist/{chunk-FE2XTCTM.js → chunk-EEP5Y5IE.js} +1 -18
- package/dist/{chunk-EOOEC32M.js → chunk-EVMY7IPL.js} +22 -0
- package/dist/{chunk-KRKSY2AE.js → chunk-F3GML67X.js} +1 -1
- package/dist/{chunk-PL4WM5XE.js → chunk-FIAQ4J45.js} +2 -2
- package/dist/{chunk-6OV3YGZF.js → chunk-FTWOVWHR.js} +2 -2
- package/dist/{chunk-457OAAL2.js → chunk-HU4PBQGJ.js} +1 -1
- package/dist/{chunk-GMJ3ILLW.js → chunk-ICMXC3MI.js} +7 -24
- package/dist/{chunk-LZT3NUTE.js → chunk-IE6O2VXV.js} +1 -1
- package/dist/{chunk-B7DJQFAC.js → chunk-IHWI3KZY.js} +4 -4
- package/dist/{chunk-WNQUXBDU.js → chunk-ILLFG4RN.js} +3 -3
- package/dist/{chunk-LEILKM2N.js → chunk-JRIXYXG3.js} +1361 -1151
- package/dist/{chunk-MNEC244Z.js → chunk-LBRJKTVY.js} +2 -2
- package/dist/{chunk-X4PK5ZXP.js → chunk-LO4DHZLH.js} +3 -3
- package/dist/{chunk-SJFA4CZ3.js → chunk-M2DZX2DP.js} +2 -2
- package/dist/{chunk-2CEIKEFU.js → chunk-M5CJQ6EH.js} +1 -1
- package/dist/{chunk-JWUKHXM6.js → chunk-MK6D6HVT.js} +1 -1
- package/dist/{chunk-BQTCQ6VN.js → chunk-MXNKK7HY.js} +3 -3
- package/dist/{chunk-BHFIITTD.js → chunk-N3U7WHQK.js} +5 -5
- package/dist/{chunk-SSYUONVH.js → chunk-NTMCSIQB.js} +2 -2
- package/dist/{chunk-BEX7K4WO.js → chunk-OINJILMB.js} +2 -2
- package/dist/{chunk-UEGULMAH.js → chunk-ORM7SJ7K.js} +7 -7
- package/dist/{chunk-2YT3SCXL.js → chunk-OXP77SHD.js} +2 -2
- package/dist/chunk-PLNQW2C6.js +3805 -0
- package/dist/{chunk-JNL3RMDG.js → chunk-PU33GPG4.js} +1 -1
- package/dist/{chunk-XXHHPB6E.js → chunk-Q73AGCTV.js} +11 -11
- package/dist/{chunk-W47D3KOB.js → chunk-RHWJKSGH.js} +1 -1
- package/dist/{chunk-M3MXTYDE.js → chunk-RJIJWETG.js} +1 -1
- package/dist/{chunk-K535ASAM.js → chunk-TINAVSGR.js} +1 -1
- package/dist/{chunk-C6TPGUWD.js → chunk-TTVHYD5R.js} +2 -2
- package/dist/{chunk-QWCXGTOH.js → chunk-UOHQV6AV.js} +24 -12
- package/dist/{chunk-5ZSLJXQX.js → chunk-UWPUEI5U.js} +2 -2
- package/dist/{chunk-4YBNQDZM.js → chunk-VCDWVVDV.js} +11 -7
- package/dist/{chunk-MXFCQCKE.js → chunk-VI7IQ2QF.js} +7 -7
- package/dist/{chunk-YWSXFFO3.js → chunk-VU4JLRNH.js} +156 -76
- package/dist/{chunk-YCRX5APY.js → chunk-WOXRCPJU.js} +1 -1
- package/dist/{chunk-PO2SDVQA.js → chunk-WQ6ZXHFQ.js} +3 -1
- package/dist/{chunk-X6HYBA4A.js → chunk-XSQWXUK2.js} +1 -1
- package/dist/{chunk-HLBAQBHT.js → chunk-XX4ZYQR6.js} +1 -1
- package/dist/{chunk-NWEER3QY.js → chunk-YH7VQAKX.js} +2 -2
- package/dist/{chunk-DZMWNCHM.js → chunk-ZAJO3YCQ.js} +2 -2
- package/dist/{clean-BDXOJ4PW.js → clean-SJ2UIDKG.js} +20 -20
- package/dist/cli.js +54 -54
- package/dist/{cloud-HVG2LUBO.js → cloud-DH7QGNPZ.js} +4 -4
- package/dist/{cloudrun-P35BPQCO.js → cloudrun-ZA2ZH2VZ.js} +10 -8
- package/dist/{compare-ZXORZDGQ.js → compare-5JNN5BHX.js} +29 -29
- package/dist/{compiler-5TFPJPQQ.js → compiler-CQ6OKYT5.js} +2 -2
- package/dist/{component-AZHVBBVP.js → component-LJQWLOXZ.js} +3 -3
- package/dist/{compositions-WFILUFC4.js → compositions-UILNO2RF.js} +11 -11
- package/dist/{delete-I34NH2Q4.js → delete-WIO2MDPB.js} +2 -2
- package/dist/deploy-BUEYC7MP.js +196 -0
- package/dist/{destroy-BEL232I5.js → destroy-QBLMYDBF.js} +6 -3
- package/dist/{dist-U2HCECNB.js → dist-MHJYD5IZ.js} +6 -4
- package/dist/{doctor-6YID47TF.js → doctor-5G2RXBQX.js} +12 -12
- package/dist/{events-G76VOIBQ.js → events-2LHNVY2F.js} +4 -4
- package/dist/{events-OKT2KO6S.js → events-BXODXMJ6.js} +2 -2
- package/dist/{feedback-EGU7SSXF.js → feedback-57END2CW.js} +7 -7
- package/dist/{ffmpeg-CSJLJAQ3.js → ffmpeg-64E4VH6W.js} +7 -7
- package/dist/{figma-SUJPNBQQ.js → figma-O5MO4GF5.js} +3 -3
- package/dist/fontLocalizeCli.js +10 -10
- package/dist/{get-7MYYFGO7.js → get-OEWPJ4HH.js} +2 -2
- package/dist/{gpuPolicy-C6JWEX7F.js → gpuPolicy-IGZOHHMX.js} +7 -7
- package/dist/{grade-compare-P22VRQLA.js → grade-compare-PG2UOMHO.js} +30 -30
- package/dist/{help-HL6QBZMC.js → help-44J4SQTE.js} +1 -1
- package/dist/{history-OG2IXOQA.js → history-CUA53AOE.js} +10 -10
- package/dist/hyperframe-runtime.js +57 -55
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +57 -55
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/{info-7P77JIWI.js → info-UDXYDQVK.js} +8 -8
- package/dist/{init-JI7QDJV4.js → init-CVT6HLW2.js} +28 -28
- package/dist/{inspect-7GSF776P.js → inspect-6F72RJQG.js} +30 -30
- package/dist/{installer-NY5E5HCO.js → installer-ADKWK4I4.js} +5 -5
- package/dist/{keyframes-6NM477BA.js → keyframes-GJ7EEWW2.js} +7 -7
- package/dist/{lambda-4DNBHUPF.js → lambda-WFSBYU6U.js} +16 -21
- package/dist/{layout-IBDEMDYP.js → layout-SASENZOG.js} +30 -30
- package/dist/{lint-BGAON4GT.js → lint-JX57B52X.js} +7 -7
- package/dist/{list-PGWNFSLX.js → list-WG7NTE63.js} +2 -2
- package/dist/{login-IAGLU3R3.js → login-3NFLD65G.js} +4 -4
- package/dist/{manager-VOGZQSK4.js → manager-BKHRATMY.js} +9 -9
- package/dist/{manager-HEUPD2YB.js → manager-V2QCCPOJ.js} +4 -3
- package/dist/{media-treatment-Z4N3JEDB.js → media-treatment-QI5F2SMU.js} +13 -13
- package/dist/{models-QUOIJVQI.js → models-TXURF3J6.js} +11 -11
- package/dist/{motionShot-GPNESXTN.js → motionShot-HRM6NMT4.js} +15 -15
- package/dist/{normalize-audio-C264N2QE.js → normalize-audio-IEKL43N4.js} +8 -8
- package/dist/{open-HB5F4HSD.js → open-GU3I75TB.js} +12 -12
- package/dist/{parakeetRunner-2ZQOINZQ.js → parakeetRunner-S2QI3PCA.js} +13 -13
- package/dist/{pipeline-JWGTH2BJ.js → pipeline-OO2MYYIV.js} +9 -9
- package/dist/{play-VQ4HA4CC.js → play-VL2WQCT3.js} +21 -21
- package/dist/{preflight-4WTT4D5X.js → preflight-GBPRLIM6.js} +9 -9
- package/dist/{present-HU46P43M.js → present-YWSFX5CH.js} +17 -17
- package/dist/{preview-LNTJ62DX.js → preview-SMG6GXLY.js} +18 -18
- package/dist/{producer-UZZQX6IJ.js → producer-67RIQCWZ.js} +1 -1
- package/dist/{projectConfig-HDALEM6S.js → projectConfig-NQQDEDAK.js} +7 -7
- package/dist/{publication-NX4VC7UU.js → publication-7DW4IJPC.js} +3 -3
- package/dist/{publish-AZRRQUXM.js → publish-FJKJ74IF.js} +10 -10
- package/dist/{remove-background-OX5WZTXY.js → remove-background-HO2NBZCJ.js} +10 -10
- package/dist/{render-HGT3EGIK.js → render-5TTZDI5N.js} +7 -14
- package/dist/{render-DV76TO7S.js → render-DH4CGGLO.js} +9 -9
- package/dist/{render-TPE44TOW.js → render-MB7PW2YQ.js} +32 -32
- package/dist/{render-batch-MUCDZ2WF.js → render-batch-4RPH66OX.js} +11 -17
- package/dist/renderSetupWorker.js +2 -2
- package/dist/{resolver-FLQ42X2Y.js → resolver-UW6NYBJA.js} +5 -5
- package/dist/{sam-IKPEJRRO.js → sam-MC24SZHC.js} +1 -1
- package/dist/{sherpa-IBMU4WV6.js → sherpa-LVXRZGE3.js} +12 -12
- package/dist/{sites-HZMADOA3.js → sites-3ZBYB5X4.js} +2 -1
- package/dist/{skills-M2ANTFD6.js → skills-NDWQPEUD.js} +6 -6
- package/dist/{skillsUpdateCheck-4WNEKKDW.js → skillsUpdateCheck-HZVZ3IUU.js} +3 -3
- package/dist/{snapshot-5AKBT5IQ.js → snapshot-MCHWMAPH.js} +33 -33
- package/dist/{src-SGPA7DM7.js → src-E34ZBMAS.js} +9 -9
- package/dist/{src-JGPFLYBL.js → src-XRFOS5LP.js} +6 -6
- package/dist/{staticProjectServer-UVFPQEUZ.js → staticProjectServer-COEAKTGH.js} +17 -17
- package/dist/studio/assets/{hyperframes-player-BulZP0_1.js → hyperframes-player-C0SyMC21.js} +1 -1
- package/dist/studio/assets/{index-CGncBUsJ.js → index-DHUassFP.js} +1 -1
- package/dist/studio/assets/{index-B1Gypobp.js → index-ueMRyAJ2.js} +173 -172
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-RVIU7JP2.js → studio-api-455XWQPD.js} +11 -11
- package/dist/{studioServer-K22MWDLW.js → studioServer-XK5N6PIB.js} +52 -67
- package/dist/{synthesize-W2M7QGDV.js → synthesize-I2C24XJ5.js} +2 -2
- package/dist/{telemetry-SUAMGBFK.js → telemetry-DD5ZIBOK.js} +4 -4
- package/dist/{timeline-SGVBFT36.js → timeline-MJJEX5CU.js} +20 -20
- package/dist/{tokens-IFHWY5WC.js → tokens-AKUZMYRE.js} +2 -2
- package/dist/{transcribe-KAOCI5CW.js → transcribe-ER7GGF4L.js} +11 -11
- package/dist/{transcribe-GY4JHB7V.js → transcribe-ZQI5P2LH.js} +17 -17
- package/dist/{tts-RE227GFP.js → tts-EDK3QS7M.js} +3 -3
- package/dist/{updateCheck-YUNDJDLW.js → updateCheck-GYFCXFWR.js} +2 -2
- package/dist/{upgrade-PL2BFWTO.js → upgrade-UNWHMZP5.js} +2 -2
- package/dist/{urlDownloader-FZ5ISJES.js → urlDownloader-QF7EBQC2.js} +6 -6
- package/dist/{validate-2NALRIFW.js → validate-32CQZFNZ.js} +24 -24
- package/dist/{video-7CDAIWYC.js → video-2KGC3RIQ.js} +7 -7
- package/package.json +1 -2
- package/dist/chunk-EEXE43II.js +0 -3805
- package/dist/chunk-TWWSYAKJ.js +0 -24
- package/dist/deploy-3TTQZGTF.js +0 -116
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
parseEncoderOptions
|
|
9
9
|
} from "./chunk-KLIKCX6Q.js";
|
|
10
10
|
import {
|
|
11
|
+
RENDER_TEMP_DIR,
|
|
11
12
|
createStudioPositionSeekReapplyScript
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-EVMY7IPL.js";
|
|
13
14
|
import {
|
|
14
15
|
GENERIC_FAMILIES,
|
|
15
16
|
collectFontFamilyCustomProperties,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
EncoderInterruptedError,
|
|
23
24
|
encoderFailureError,
|
|
24
25
|
prepareAnimatedGifInputs
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-FIAQ4J45.js";
|
|
26
27
|
import {
|
|
27
28
|
BROWSER_GPU_NOT_SOFTWARE,
|
|
28
29
|
CaptureFailure,
|
|
@@ -151,7 +152,7 @@ import {
|
|
|
151
152
|
validateEngineConfigSnapshot,
|
|
152
153
|
verifyDiskDrawElementSamples,
|
|
153
154
|
writeCapturedFrame
|
|
154
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-VU4JLRNH.js";
|
|
155
156
|
import {
|
|
156
157
|
assignBundledRuntimeCompositionIds,
|
|
157
158
|
buildVariablesByCompScript,
|
|
@@ -164,7 +165,7 @@ import {
|
|
|
164
165
|
isTemplateEntry,
|
|
165
166
|
parsesAsScript,
|
|
166
167
|
prepareFlattenedInnerRoot
|
|
167
|
-
} from "./chunk-
|
|
168
|
+
} from "./chunk-PU33GPG4.js";
|
|
168
169
|
import {
|
|
169
170
|
LOW_MEMORY_TOTAL_MB_THRESHOLD,
|
|
170
171
|
getCgroupMemoryLimitMb,
|
|
@@ -190,7 +191,7 @@ import {
|
|
|
190
191
|
resolveAbsoluteMediaStartSeconds,
|
|
191
192
|
resolveAuthoredTimingWindow,
|
|
192
193
|
toFps
|
|
193
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-AHS5QYKF.js";
|
|
194
195
|
import {
|
|
195
196
|
COMPOSITION_SOURCE_URL,
|
|
196
197
|
HF_COLOR_GRADING_ATTR,
|
|
@@ -218,7 +219,7 @@ import {
|
|
|
218
219
|
shouldClampResolvedMediaDuration,
|
|
219
220
|
sourceTimeAt,
|
|
220
221
|
styleElementsFor
|
|
221
|
-
} from "./chunk-
|
|
222
|
+
} from "./chunk-PLNQW2C6.js";
|
|
222
223
|
import {
|
|
223
224
|
MAX_AUDIO_GAIN
|
|
224
225
|
} from "./chunk-UJKQWGFF.js";
|
|
@@ -236,10 +237,10 @@ import {
|
|
|
236
237
|
import {
|
|
237
238
|
redactKnownPaths,
|
|
238
239
|
redactTelemetryString
|
|
239
|
-
} from "./chunk-
|
|
240
|
+
} from "./chunk-634HSJ3J.js";
|
|
240
241
|
import {
|
|
241
242
|
chromeMajorCeiling
|
|
242
|
-
} from "./chunk-
|
|
243
|
+
} from "./chunk-537IYAEE.js";
|
|
243
244
|
import {
|
|
244
245
|
lintHyperframeHtml
|
|
245
246
|
} from "./chunk-GK2FIKIQ.js";
|
|
@@ -1582,7 +1583,6 @@ import { hostname, uptime } from "os";
|
|
|
1582
1583
|
import { dirname as dirname3, join as join4 } from "path";
|
|
1583
1584
|
var OWNER_FILE = "owner.json";
|
|
1584
1585
|
var TRANSACTION_BACKUP = "backup";
|
|
1585
|
-
var RENDER_TEMP_DIR = /^(work-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}|hf-render|\..+\.hf-transaction)-[A-Za-z0-9]{6}$/;
|
|
1586
1586
|
var RENDER_JOB_DIR = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
|
|
1587
1587
|
function pidScope() {
|
|
1588
1588
|
const attempt = (read) => {
|
|
@@ -5756,7 +5756,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
5756
5756
|
}
|
|
5757
5757
|
return false;
|
|
5758
5758
|
}
|
|
5759
|
-
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log) {
|
|
5759
|
+
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
5760
5760
|
let filePath = src;
|
|
5761
5761
|
if (isHttpUrl(src)) {
|
|
5762
5762
|
if (!existsSync9(downloadDir)) mkdirSync5(downloadDir, { recursive: true });
|
|
@@ -5770,8 +5770,9 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
5770
5770
|
} else {
|
|
5771
5771
|
filePath = decodedUrlPath(src);
|
|
5772
5772
|
if (!filePath.startsWith("/")) filePath = join12(baseDir, filePath);
|
|
5773
|
+
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
5773
5774
|
}
|
|
5774
|
-
if (!existsSync9(filePath)) {
|
|
5775
|
+
if (!isHttpUrl(filePath) && !existsSync9(filePath)) {
|
|
5775
5776
|
return { duration: null, resolvedPath: filePath };
|
|
5776
5777
|
}
|
|
5777
5778
|
const withSrcContext = (error) => {
|
|
@@ -5834,7 +5835,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
|
5834
5835
|
}
|
|
5835
5836
|
return changed ? document2.toString() : html;
|
|
5836
5837
|
}
|
|
5837
|
-
async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
5838
|
+
async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
|
|
5838
5839
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
|
|
5839
5840
|
const mediaUnresolved = unresolved.filter(
|
|
5840
5841
|
(el) => (el.tagName === "video" || el.tagName === "audio") && el.src
|
|
@@ -5850,7 +5851,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
|
5850
5851
|
downloadDir,
|
|
5851
5852
|
el.tagName,
|
|
5852
5853
|
el.id,
|
|
5853
|
-
log
|
|
5854
|
+
log,
|
|
5855
|
+
urlByAbsentLocalPath
|
|
5854
5856
|
).then(({ duration }) => ({ id: el.id, duration }))
|
|
5855
5857
|
)
|
|
5856
5858
|
);
|
|
@@ -5892,7 +5894,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
|
|
|
5892
5894
|
compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
|
|
5893
5895
|
return { html: compiledHtml, unresolvedCompositions };
|
|
5894
5896
|
}
|
|
5895
|
-
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set()) {
|
|
5897
|
+
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
5896
5898
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
5897
5899
|
const { document: document2 } = parseHTML(html);
|
|
5898
5900
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
@@ -5918,13 +5920,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
5918
5920
|
const { html: compiledSub } = await compileHtmlFile(
|
|
5919
5921
|
item.rawSubHtml,
|
|
5920
5922
|
dirname5(item.filePath),
|
|
5921
|
-
downloadDir
|
|
5923
|
+
downloadDir,
|
|
5924
|
+
void 0,
|
|
5925
|
+
urlByAbsentLocalPath
|
|
5922
5926
|
);
|
|
5923
5927
|
const nested = await parseSubCompositions(
|
|
5924
5928
|
compiledSub,
|
|
5925
5929
|
projectDir,
|
|
5926
5930
|
downloadDir,
|
|
5927
|
-
item.nestedVisited
|
|
5931
|
+
item.nestedVisited,
|
|
5932
|
+
urlByAbsentLocalPath
|
|
5928
5933
|
);
|
|
5929
5934
|
return {
|
|
5930
5935
|
srcPath: item.srcPath,
|
|
@@ -6682,9 +6687,16 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
6682
6687
|
rawHtml,
|
|
6683
6688
|
projectDir,
|
|
6684
6689
|
downloadDir,
|
|
6685
|
-
options.log
|
|
6690
|
+
options.log,
|
|
6691
|
+
options.urlByAbsentLocalPath
|
|
6692
|
+
);
|
|
6693
|
+
const { subCompositions } = await parseSubCompositions(
|
|
6694
|
+
compiledHtml,
|
|
6695
|
+
projectDir,
|
|
6696
|
+
downloadDir,
|
|
6697
|
+
void 0,
|
|
6698
|
+
options.urlByAbsentLocalPath
|
|
6686
6699
|
);
|
|
6687
|
-
const { subCompositions } = await parseSubCompositions(compiledHtml, projectDir, downloadDir);
|
|
6688
6700
|
const fullHtml = ensureFullDocument(compiledHtml);
|
|
6689
6701
|
const inlinedHtml = inlineSubCompositions2(
|
|
6690
6702
|
fullHtml,
|
|
@@ -7180,6 +7192,7 @@ async function runCompileStage(input) {
|
|
|
7180
7192
|
allowSystemFontCapture,
|
|
7181
7193
|
abortSignal,
|
|
7182
7194
|
variables: input.variables,
|
|
7195
|
+
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7183
7196
|
animatedGifCacheDir: cfg.extractCacheDir ? join13(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7184
7197
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7185
7198
|
});
|
|
@@ -8148,7 +8161,8 @@ async function runExtractVideosStage(input) {
|
|
|
8148
8161
|
abortSignal,
|
|
8149
8162
|
assertNotAborted,
|
|
8150
8163
|
materializeSymlinks,
|
|
8151
|
-
deferRangeExtraction
|
|
8164
|
+
deferRangeExtraction,
|
|
8165
|
+
urlByAbsentLocalPath
|
|
8152
8166
|
} = input;
|
|
8153
8167
|
const stage2Start = Date.now();
|
|
8154
8168
|
const extractionPolicy = resolveVideoExtractionPolicy();
|
|
@@ -8242,7 +8256,8 @@ async function runExtractVideosStage(input) {
|
|
|
8242
8256
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
8243
8257
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
8244
8258
|
deferRangeExtraction,
|
|
8245
|
-
contentKeyedDirs: [join15(compiledDir, REMOTE_MEDIA_SUBDIR)]
|
|
8259
|
+
contentKeyedDirs: [join15(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
8260
|
+
urlByAbsentLocalPath
|
|
8246
8261
|
},
|
|
8247
8262
|
abortSignal,
|
|
8248
8263
|
cfg,
|
|
@@ -8320,6 +8335,7 @@ async function runAudioStage(input) {
|
|
|
8320
8335
|
audioGain,
|
|
8321
8336
|
audios,
|
|
8322
8337
|
masterFxChain,
|
|
8338
|
+
urlByAbsentLocalPath,
|
|
8323
8339
|
abortSignal,
|
|
8324
8340
|
assertNotAborted,
|
|
8325
8341
|
log
|
|
@@ -8339,7 +8355,7 @@ async function runAudioStage(input) {
|
|
|
8339
8355
|
audioOutputPath,
|
|
8340
8356
|
duration,
|
|
8341
8357
|
abortSignal,
|
|
8342
|
-
{ ffmpegProcessTimeout, audioGain, masterFxChain },
|
|
8358
|
+
{ ffmpegProcessTimeout, audioGain, masterFxChain, urlByAbsentLocalPath },
|
|
8343
8359
|
compiledDir
|
|
8344
8360
|
);
|
|
8345
8361
|
} catch (err) {
|
|
@@ -13151,14 +13167,14 @@ async function executeRenderPipeline(input) {
|
|
|
13151
13167
|
import {
|
|
13152
13168
|
copyFileSync as copyFileSync4,
|
|
13153
13169
|
cpSync as cpSync2,
|
|
13154
|
-
existsSync as
|
|
13170
|
+
existsSync as existsSync20,
|
|
13155
13171
|
linkSync,
|
|
13156
|
-
mkdirSync as
|
|
13157
|
-
renameSync as
|
|
13158
|
-
rmSync as
|
|
13159
|
-
writeFileSync as
|
|
13172
|
+
mkdirSync as mkdirSync12,
|
|
13173
|
+
renameSync as renameSync5,
|
|
13174
|
+
rmSync as rmSync10,
|
|
13175
|
+
writeFileSync as writeFileSync8
|
|
13160
13176
|
} from "fs";
|
|
13161
|
-
import { extname as extname6, isAbsolute as isAbsolute2, join as
|
|
13177
|
+
import { extname as extname6, isAbsolute as isAbsolute2, join as join33, relative as relative5, sep as sep2 } from "path";
|
|
13162
13178
|
|
|
13163
13179
|
// ../producer/src/services/distributed/planFrameCost.ts
|
|
13164
13180
|
import { join as join28 } from "path";
|
|
@@ -13539,6 +13555,8 @@ function buildChunkCaptureOptions(input) {
|
|
|
13539
13555
|
// The controller's snapshot, so the first capture sees the in-process renderer's `__hfVariables`.
|
|
13540
13556
|
variables: encoder.variables,
|
|
13541
13557
|
subTimelineWaitMemo: {},
|
|
13558
|
+
skipReadinessVideoIds: input.skipReadinessVideoIds,
|
|
13559
|
+
videoMetadataHints: input.videoMetadataHints,
|
|
13542
13560
|
...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
|
|
13543
13561
|
// A fixed warmup count keeps `beginFrameTimeTicks` host-independent.
|
|
13544
13562
|
lockWarmupTicks: true,
|
|
@@ -13547,6 +13565,19 @@ function buildChunkCaptureOptions(input) {
|
|
|
13547
13565
|
}
|
|
13548
13566
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
13549
13567
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
13568
|
+
const remoteSources = input.remoteSources ?? {};
|
|
13569
|
+
for (const path of Object.keys(remoteSources)) {
|
|
13570
|
+
if (!deferred.some((video) => video.deferredRange.sourcePath === path)) {
|
|
13571
|
+
throw new RenderChunkValidationError(
|
|
13572
|
+
MISSING_PLAN_ARTIFACT,
|
|
13573
|
+
`[renderChunk] ${path} is read by URL but no deferred video uses it`
|
|
13574
|
+
);
|
|
13575
|
+
}
|
|
13576
|
+
defaultLogger.info("[renderChunk] reading video by URL", {
|
|
13577
|
+
path,
|
|
13578
|
+
url: remoteSources[path].split("?")[0]
|
|
13579
|
+
});
|
|
13580
|
+
}
|
|
13550
13581
|
if (deferred.length === 0) return [];
|
|
13551
13582
|
const table = createFrameLookupTable(
|
|
13552
13583
|
input.planVideos.videos,
|
|
@@ -13574,8 +13605,9 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
13574
13605
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
13575
13606
|
}
|
|
13576
13607
|
const range = video.deferredRange;
|
|
13577
|
-
const
|
|
13578
|
-
|
|
13608
|
+
const remoteUrl = remoteSources[range.sourcePath];
|
|
13609
|
+
const sourcePath = remoteUrl ?? join27(input.planDir, ...range.sourcePath.split("/"));
|
|
13610
|
+
if (remoteUrl === void 0 && !existsSync17(sourcePath)) {
|
|
13579
13611
|
throw new RenderChunkValidationError(
|
|
13580
13612
|
MISSING_PLAN_ARTIFACT,
|
|
13581
13613
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -13915,6 +13947,16 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
13915
13947
|
}
|
|
13916
13948
|
return { compositionHtml, assets };
|
|
13917
13949
|
}
|
|
13950
|
+
var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
13951
|
+
function writePlanRemoteFiles(planDir, files) {
|
|
13952
|
+
if (files.length === 0) return;
|
|
13953
|
+
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
13954
|
+
writeFileSync6(join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
13955
|
+
}
|
|
13956
|
+
function readPlanRemoteFiles(planDir) {
|
|
13957
|
+
const path = join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
13958
|
+
return existsSync18(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
|
|
13959
|
+
}
|
|
13918
13960
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
13919
13961
|
const planJsonPath = join29(planDir, "plan.json");
|
|
13920
13962
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
@@ -14030,170 +14072,679 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
14030
14072
|
};
|
|
14031
14073
|
}
|
|
14032
14074
|
|
|
14033
|
-
// ../producer/src/services/distributed/
|
|
14034
|
-
import {
|
|
14035
|
-
import {
|
|
14036
|
-
import {
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
[PLAN_AUDIO_RELATIVE_PATH]: "audio",
|
|
14060
|
-
meta: "metadata",
|
|
14061
|
-
"plan.json": "metadata"
|
|
14062
|
-
};
|
|
14063
|
-
function hashComponent(value) {
|
|
14064
|
-
return createHash6("sha256").update(value).digest("hex").slice(0, 12);
|
|
14075
|
+
// ../producer/src/services/distributed/chunkAudio.ts
|
|
14076
|
+
import { closeSync as closeSync6, existsSync as existsSync19, ftruncateSync, mkdirSync as mkdirSync11, openSync as openSync5, readFileSync as readFileSync10 } from "fs";
|
|
14077
|
+
import { writeFileSync as writeFileSync7, writeSync } from "fs";
|
|
14078
|
+
import { join as join31 } from "path";
|
|
14079
|
+
|
|
14080
|
+
// ../producer/src/services/distributed/mp4Stitch.ts
|
|
14081
|
+
import {
|
|
14082
|
+
createReadStream as createReadStream4,
|
|
14083
|
+
createWriteStream,
|
|
14084
|
+
mkdirSync as mkdirSync10,
|
|
14085
|
+
mkdtempSync as mkdtempSync4,
|
|
14086
|
+
renameSync as renameSync4,
|
|
14087
|
+
rmSync as rmSync9
|
|
14088
|
+
} from "fs";
|
|
14089
|
+
import { open } from "fs/promises";
|
|
14090
|
+
import { dirname as dirname11, join as join30 } from "path";
|
|
14091
|
+
import { finished, pipeline } from "stream/promises";
|
|
14092
|
+
function boxAt(src, off, at, limit) {
|
|
14093
|
+
let size = src.readUInt32BE(off);
|
|
14094
|
+
let bodyStart = at + 8;
|
|
14095
|
+
if (size === 1) {
|
|
14096
|
+
size = Number(src.readBigUInt64BE(off + 8));
|
|
14097
|
+
bodyStart = at + 16;
|
|
14098
|
+
}
|
|
14099
|
+
if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
|
|
14100
|
+
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
14065
14101
|
}
|
|
14066
|
-
function
|
|
14067
|
-
const
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
const videoKey = segments[1] ?? "unknown";
|
|
14071
|
-
return {
|
|
14072
|
-
category: "video-frames",
|
|
14073
|
-
componentLabel: `video-frames:${hashComponent(videoKey)}`
|
|
14074
|
-
};
|
|
14102
|
+
function childBoxes(buf, parent) {
|
|
14103
|
+
const boxes = [];
|
|
14104
|
+
for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
|
|
14105
|
+
boxes.push(boxAt(buf, at, at, parent.end));
|
|
14075
14106
|
}
|
|
14076
|
-
|
|
14077
|
-
return { category, componentLabel: category };
|
|
14107
|
+
return boxes;
|
|
14078
14108
|
}
|
|
14079
|
-
function
|
|
14080
|
-
return
|
|
14081
|
-
totalBytes: 0,
|
|
14082
|
-
fileCount: 0,
|
|
14083
|
-
compiledBytes: 0,
|
|
14084
|
-
sourceMediaBytes: 0,
|
|
14085
|
-
videoFramesBytes: 0,
|
|
14086
|
-
videoFrameFileCount: 0,
|
|
14087
|
-
audioBytes: 0,
|
|
14088
|
-
metadataBytes: 0,
|
|
14089
|
-
otherBytes: 0,
|
|
14090
|
-
topComponents: []
|
|
14091
|
-
};
|
|
14109
|
+
function findChild(buf, parent, type) {
|
|
14110
|
+
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
14092
14111
|
}
|
|
14093
|
-
function
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
result.sourceMediaBytes += sizeBytes;
|
|
14098
|
-
}
|
|
14099
|
-
return;
|
|
14100
|
-
}
|
|
14101
|
-
if (category === "video-frames") {
|
|
14102
|
-
result.videoFramesBytes += sizeBytes;
|
|
14103
|
-
result.videoFrameFileCount += 1;
|
|
14104
|
-
return;
|
|
14105
|
-
}
|
|
14106
|
-
if (category === "audio") {
|
|
14107
|
-
result.audioBytes += sizeBytes;
|
|
14108
|
-
return;
|
|
14109
|
-
}
|
|
14110
|
-
if (category === "metadata") {
|
|
14111
|
-
result.metadataBytes += sizeBytes;
|
|
14112
|
-
return;
|
|
14113
|
-
}
|
|
14114
|
-
result.otherBytes += sizeBytes;
|
|
14112
|
+
function child(buf, parent, type) {
|
|
14113
|
+
const found = findChild(buf, parent, type);
|
|
14114
|
+
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
14115
|
+
return found;
|
|
14115
14116
|
}
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
return;
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
14127
|
-
const classified = classifyFile(segments, rootKind);
|
|
14128
|
-
result.totalBytes += sizeBytes;
|
|
14129
|
-
result.fileCount += 1;
|
|
14130
|
-
addCategoryBytes(result, classified.category, filePath, sizeBytes);
|
|
14131
|
-
const component = components.get(classified.componentLabel) ?? {
|
|
14132
|
-
label: classified.componentLabel,
|
|
14133
|
-
sizeBytes: 0,
|
|
14134
|
-
fileCount: 0
|
|
14117
|
+
var SCAN_BYTES = 64 * 1024;
|
|
14118
|
+
async function readMp4Layout(read) {
|
|
14119
|
+
const found = /* @__PURE__ */ new Map();
|
|
14120
|
+
let payload;
|
|
14121
|
+
let buf = Buffer.alloc(0);
|
|
14122
|
+
let bufAt = 0;
|
|
14123
|
+
const load = async (start, end) => {
|
|
14124
|
+
if (start >= bufAt && end <= bufAt + buf.length) return;
|
|
14125
|
+
buf = await read(start, Math.max(end, start + SCAN_BYTES));
|
|
14126
|
+
bufAt = start;
|
|
14135
14127
|
};
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
}
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
}
|
|
14147
|
-
for (const entry of entries) {
|
|
14148
|
-
const full = join30(dir, entry.name);
|
|
14149
|
-
if (entry.isDirectory()) {
|
|
14150
|
-
walkRegularFiles(full, visit);
|
|
14151
|
-
} else if (entry.isFile()) {
|
|
14152
|
-
visit(full);
|
|
14128
|
+
for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
|
|
14129
|
+
await load(at, at + 16);
|
|
14130
|
+
if (at + 8 > bufAt + buf.length) {
|
|
14131
|
+
throw new Error(`[mp4Stitch] chunk needs ftyp, moov and mdat; found ${[...found.keys()]}`);
|
|
14132
|
+
}
|
|
14133
|
+
const box2 = boxAt(buf, at - bufAt, at, Number.MAX_SAFE_INTEGER);
|
|
14134
|
+
if (box2.type === "mdat") payload ??= box2;
|
|
14135
|
+
if (box2.type === "ftyp" || box2.type === "moov") {
|
|
14136
|
+
await load(at, box2.end);
|
|
14137
|
+
found.set(box2.type, buf.subarray(at - bufAt, box2.end - bufAt));
|
|
14153
14138
|
}
|
|
14139
|
+
at = box2.end;
|
|
14154
14140
|
}
|
|
14141
|
+
const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
|
|
14142
|
+
if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
|
|
14143
|
+
return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
|
|
14155
14144
|
}
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
const
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14145
|
+
var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
|
|
14146
|
+
function readTracks(moov) {
|
|
14147
|
+
const root = boxAt(moov, 0, 0, moov.length);
|
|
14148
|
+
return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
|
|
14149
|
+
}
|
|
14150
|
+
function rows(buf, table, columns, countAt = 4) {
|
|
14151
|
+
const count = buf.readUInt32BE(table.bodyStart + countAt);
|
|
14152
|
+
const first = table.bodyStart + countAt + 4;
|
|
14153
|
+
return Array.from(
|
|
14154
|
+
{ length: count },
|
|
14155
|
+
(_, row) => Array.from(
|
|
14156
|
+
{ length: columns },
|
|
14157
|
+
(_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
|
|
14158
|
+
)
|
|
14162
14159
|
);
|
|
14163
|
-
result.topComponents = [...components.values()].sort((a, b) => b.sizeBytes - a.sizeBytes || a.label.localeCompare(b.label)).slice(0, MAX_TOP_COMPONENTS);
|
|
14164
|
-
return result;
|
|
14165
14160
|
}
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
|
|
14169
|
-
|
|
14161
|
+
function readMediaTime(moov, trak) {
|
|
14162
|
+
const edts = findChild(moov, trak, "edts");
|
|
14163
|
+
if (!edts) return 0;
|
|
14164
|
+
const elst = child(moov, edts, "elst");
|
|
14165
|
+
const wide = moov[elst.bodyStart] === 1;
|
|
14166
|
+
const entries = moov.readUInt32BE(elst.bodyStart + 4);
|
|
14167
|
+
for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
|
|
14168
|
+
const offset = at + (wide ? 8 : 4);
|
|
14169
|
+
const mediaTime = wide ? Number(moov.readBigInt64BE(offset)) : moov.readInt32BE(offset);
|
|
14170
|
+
if (mediaTime !== -1) return mediaTime;
|
|
14171
|
+
}
|
|
14172
|
+
return 0;
|
|
14170
14173
|
}
|
|
14171
|
-
function
|
|
14172
|
-
const
|
|
14173
|
-
|
|
14174
|
-
|
|
14175
|
-
job,
|
|
14176
|
-
[
|
|
14177
|
-
{
|
|
14178
|
-
code: "audio_processing_failed",
|
|
14179
|
-
message: `Audio mix failed; output would be video-only: ${audioError}`,
|
|
14180
|
-
details: {
|
|
14181
|
-
mediaType: "audio",
|
|
14182
|
-
audioFailures: audioFailureDiagnostics(audioFailures),
|
|
14183
|
-
failureReasons: [...new Set(audioFailures.map((failure) => failure.reason))],
|
|
14184
|
-
failureStages: [...new Set(audioFailures.map((failure) => failure.stage))],
|
|
14185
|
-
failureOwner,
|
|
14186
|
-
retryable
|
|
14187
|
-
}
|
|
14188
|
-
}
|
|
14189
|
-
],
|
|
14190
|
-
log
|
|
14191
|
-
);
|
|
14174
|
+
function readSizes(moov, stsz) {
|
|
14175
|
+
const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
|
|
14176
|
+
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
14177
|
+
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
14192
14178
|
}
|
|
14193
|
-
|
|
14194
|
-
"
|
|
14195
|
-
|
|
14196
|
-
|
|
14179
|
+
function readSync4(moov, stbl, sampleCount) {
|
|
14180
|
+
const stss = findChild(moov, stbl, "stss");
|
|
14181
|
+
if (!stss) return null;
|
|
14182
|
+
const sync = Array(sampleCount).fill(false);
|
|
14183
|
+
for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
|
|
14184
|
+
return sync;
|
|
14185
|
+
}
|
|
14186
|
+
function readChunkOffsets(moov, stbl) {
|
|
14187
|
+
const stco = findChild(moov, stbl, "stco");
|
|
14188
|
+
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
14189
|
+
return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
14190
|
+
}
|
|
14191
|
+
function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
14192
|
+
const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
|
|
14193
|
+
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
14194
|
+
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
14195
|
+
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
14196
|
+
return Array(nextFirst - firstChunk).fill(perChunk);
|
|
14197
|
+
});
|
|
14198
|
+
}
|
|
14199
|
+
function readTrack(moov, trak) {
|
|
14200
|
+
const mdia = child(moov, trak, "mdia");
|
|
14201
|
+
const hdlr = child(moov, mdia, "hdlr");
|
|
14202
|
+
const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
|
|
14203
|
+
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
14204
|
+
const stts = rows(moov, child(moov, stbl, "stts"), 2);
|
|
14205
|
+
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
14206
|
+
const sizes = readSizes(moov, child(moov, stbl, "stsz"));
|
|
14207
|
+
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
14208
|
+
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
14209
|
+
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
14210
|
+
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
14211
|
+
}
|
|
14212
|
+
const stsd = child(moov, stbl, "stsd");
|
|
14213
|
+
return {
|
|
14214
|
+
trak,
|
|
14215
|
+
handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
|
|
14216
|
+
timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
|
|
14217
|
+
mediaTime: readMediaTime(moov, trak),
|
|
14218
|
+
stsd: moov.subarray(stsd.start, stsd.end),
|
|
14219
|
+
durations,
|
|
14220
|
+
sizes,
|
|
14221
|
+
sync: readSync4(moov, stbl, sizes.length),
|
|
14222
|
+
samplesPerChunk,
|
|
14223
|
+
chunkOffsets
|
|
14224
|
+
};
|
|
14225
|
+
}
|
|
14226
|
+
function sum(values) {
|
|
14227
|
+
return values.reduce((total, value) => total + value, 0);
|
|
14228
|
+
}
|
|
14229
|
+
function box(type, ...body) {
|
|
14230
|
+
const header = Buffer.alloc(8);
|
|
14231
|
+
header.writeUInt32BE(8 + sum(body.map((part) => part.length)));
|
|
14232
|
+
header.write(type, 4, "latin1");
|
|
14233
|
+
return Buffer.concat([header, ...body]);
|
|
14234
|
+
}
|
|
14235
|
+
function fullBox(type, version, body) {
|
|
14236
|
+
return box(type, Buffer.from([version, 0, 0, 0]), body);
|
|
14237
|
+
}
|
|
14238
|
+
function u32s(values) {
|
|
14239
|
+
const buf = Buffer.alloc(values.length * 4);
|
|
14240
|
+
values.forEach((value, i) => buf.writeUInt32BE(value, i * 4));
|
|
14241
|
+
return buf;
|
|
14242
|
+
}
|
|
14243
|
+
function runs(values) {
|
|
14244
|
+
const out = [];
|
|
14245
|
+
for (const value of values) {
|
|
14246
|
+
const last = out.at(-1);
|
|
14247
|
+
if (last && last[1] === value) last[0]++;
|
|
14248
|
+
else out.push([1, value]);
|
|
14249
|
+
}
|
|
14250
|
+
return out;
|
|
14251
|
+
}
|
|
14252
|
+
function rebuild(buf, parent, replace) {
|
|
14253
|
+
const parts = childBoxes(buf, parent).map(
|
|
14254
|
+
(box2) => replace[box2.type]?.(box2) ?? buf.subarray(box2.start, box2.end)
|
|
14255
|
+
);
|
|
14256
|
+
return box(parent.type, ...parts);
|
|
14257
|
+
}
|
|
14258
|
+
var DURATION_AT = {
|
|
14259
|
+
mvhd: [16, 24],
|
|
14260
|
+
mdhd: [16, 24],
|
|
14261
|
+
tkhd: [20, 28]
|
|
14262
|
+
};
|
|
14263
|
+
function withDuration(buf, target, duration) {
|
|
14264
|
+
const copy = Buffer.from(buf.subarray(target.start, target.end));
|
|
14265
|
+
const body = target.bodyStart - target.start;
|
|
14266
|
+
const wide = copy[body] === 1;
|
|
14267
|
+
const at = body + DURATION_AT[target.type][wide ? 1 : 0];
|
|
14268
|
+
if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
|
|
14269
|
+
else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
|
|
14270
|
+
else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
|
|
14271
|
+
return copy;
|
|
14272
|
+
}
|
|
14273
|
+
function timescaleOf(buf, header) {
|
|
14274
|
+
return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
|
|
14275
|
+
}
|
|
14276
|
+
function buildStbl(stsd, track) {
|
|
14277
|
+
const stts = runs(track.durations);
|
|
14278
|
+
const stsc = [];
|
|
14279
|
+
let firstChunk = 1;
|
|
14280
|
+
for (const [count, perChunk] of runs(track.samplesPerChunk)) {
|
|
14281
|
+
stsc.push([firstChunk, perChunk, 1]);
|
|
14282
|
+
firstChunk += count;
|
|
14283
|
+
}
|
|
14284
|
+
const co64 = Buffer.alloc(4 + track.chunkOffsets.length * 8);
|
|
14285
|
+
co64.writeUInt32BE(track.chunkOffsets.length);
|
|
14286
|
+
track.chunkOffsets.forEach((offset, i) => co64.writeBigUInt64BE(BigInt(offset), 4 + i * 8));
|
|
14287
|
+
const syncSamples = track.sync?.flatMap((isSync, i) => isSync ? [i + 1] : []) ?? null;
|
|
14288
|
+
return box(
|
|
14289
|
+
"stbl",
|
|
14290
|
+
stsd,
|
|
14291
|
+
fullBox("stts", 0, u32s([stts.length, ...stts.flat()])),
|
|
14292
|
+
...syncSamples ? [fullBox("stss", 0, u32s([syncSamples.length, ...syncSamples]))] : [],
|
|
14293
|
+
fullBox("stsc", 0, u32s([stsc.length, ...stsc.flat()])),
|
|
14294
|
+
fullBox("stsz", 0, u32s([0, track.sizes.length, ...track.sizes])),
|
|
14295
|
+
fullBox("co64", 0, co64)
|
|
14296
|
+
);
|
|
14297
|
+
}
|
|
14298
|
+
var codecOf = (track) => track.stsd.toString("latin1", 20, 24);
|
|
14299
|
+
var sameTrack = (track, plan2) => track.handler === plan2.handler && track.timescale === plan2.timescale && codecOf(track) === codecOf(plan2);
|
|
14300
|
+
function planTracksOf(tracks, plan2, index) {
|
|
14301
|
+
let next = 0;
|
|
14302
|
+
const matched = plan2.map((planTrack) => {
|
|
14303
|
+
const track = tracks[next];
|
|
14304
|
+
if (track && sameTrack(track, planTrack)) return tracks[next++];
|
|
14305
|
+
if (planTrack.handler === "soun") return null;
|
|
14306
|
+
throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
|
|
14307
|
+
});
|
|
14308
|
+
if (next !== tracks.length)
|
|
14309
|
+
throw new Error(`[mp4Stitch] chunk ${index} tracks differ from chunk 0`);
|
|
14310
|
+
for (const track of tracks) {
|
|
14311
|
+
if (track.handler === "vide" && track.mediaTime !== 0) {
|
|
14312
|
+
throw new Error(`[mp4Stitch] chunk ${index} video starts at media time ${track.mediaTime}`);
|
|
14313
|
+
}
|
|
14314
|
+
}
|
|
14315
|
+
return matched;
|
|
14316
|
+
}
|
|
14317
|
+
function buildStitchedHeader(layouts) {
|
|
14318
|
+
const plan2 = readTracks(layouts[0].moov);
|
|
14319
|
+
const chunks = layouts.map((layout, i) => planTracksOf(readTracks(layout.moov), plan2, i));
|
|
14320
|
+
const payloadBases = [];
|
|
14321
|
+
let payloadBytes = 0;
|
|
14322
|
+
for (const layout of layouts) {
|
|
14323
|
+
payloadBases.push(payloadBytes);
|
|
14324
|
+
payloadBytes += layout.payloadEnd - layout.payloadStart;
|
|
14325
|
+
}
|
|
14326
|
+
const template = layouts[0].moov;
|
|
14327
|
+
const root = boxAt(template, 0, 0, template.length);
|
|
14328
|
+
const movieTimescale = timescaleOf(template, child(template, root, "mvhd"));
|
|
14329
|
+
const buildMoov = (dataStart2) => {
|
|
14330
|
+
const traks = plan2.map((track, t) => {
|
|
14331
|
+
const parts = chunks.flatMap((tracks, chunk) => {
|
|
14332
|
+
const part = tracks[t];
|
|
14333
|
+
return part ? [{ ...part, chunk }] : [];
|
|
14334
|
+
});
|
|
14335
|
+
const merged = {
|
|
14336
|
+
durations: parts.flatMap((part) => part.durations),
|
|
14337
|
+
sizes: parts.flatMap((part) => part.sizes),
|
|
14338
|
+
sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
|
|
14339
|
+
samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
|
|
14340
|
+
chunkOffsets: parts.flatMap(
|
|
14341
|
+
(part) => part.chunkOffsets.map((offset) => {
|
|
14342
|
+
const i = part.chunk;
|
|
14343
|
+
const { payloadStart, payloadEnd } = layouts[i];
|
|
14344
|
+
if (offset < payloadStart || offset >= payloadEnd) {
|
|
14345
|
+
throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
|
|
14346
|
+
}
|
|
14347
|
+
return offset - payloadStart + payloadBases[i] + dataStart2;
|
|
14348
|
+
})
|
|
14349
|
+
)
|
|
14350
|
+
};
|
|
14351
|
+
const mediaDuration = sum(merged.durations);
|
|
14352
|
+
const shown = Math.round(
|
|
14353
|
+
(mediaDuration - track.mediaTime) * movieTimescale / track.timescale
|
|
14354
|
+
);
|
|
14355
|
+
const elst = Buffer.alloc(24);
|
|
14356
|
+
elst.writeUInt32BE(1);
|
|
14357
|
+
elst.writeBigUInt64BE(BigInt(shown), 4);
|
|
14358
|
+
elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
|
|
14359
|
+
elst.writeUInt32BE(65536, 20);
|
|
14360
|
+
const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
|
|
14361
|
+
const trak = rebuild(template, track.trak, {
|
|
14362
|
+
tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
|
|
14363
|
+
edts: () => Buffer.alloc(0),
|
|
14364
|
+
mdia: (mdia) => rebuild(template, mdia, {
|
|
14365
|
+
mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
|
|
14366
|
+
minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
|
|
14367
|
+
})
|
|
14368
|
+
});
|
|
14369
|
+
return { trak, shown };
|
|
14370
|
+
});
|
|
14371
|
+
let next = 0;
|
|
14372
|
+
return rebuild(template, root, {
|
|
14373
|
+
mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
|
|
14374
|
+
trak: () => traks[next++].trak
|
|
14375
|
+
});
|
|
14376
|
+
};
|
|
14377
|
+
const ftyp = layouts[0].ftyp;
|
|
14378
|
+
const dataStart = ftyp.length + buildMoov(0).length + 16;
|
|
14379
|
+
const mdat = Buffer.alloc(16);
|
|
14380
|
+
mdat.writeUInt32BE(1);
|
|
14381
|
+
mdat.write("mdat", 4, "latin1");
|
|
14382
|
+
mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
|
|
14383
|
+
return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
|
|
14384
|
+
}
|
|
14385
|
+
function fileRange(path) {
|
|
14386
|
+
return async (start, end) => {
|
|
14387
|
+
const file = await open(path, "r", 384);
|
|
14388
|
+
try {
|
|
14389
|
+
const buf = Buffer.alloc(end - start);
|
|
14390
|
+
const { bytesRead } = await file.read(buf, 0, buf.length, start);
|
|
14391
|
+
return buf.subarray(0, bytesRead);
|
|
14392
|
+
} finally {
|
|
14393
|
+
await file.close();
|
|
14394
|
+
}
|
|
14395
|
+
};
|
|
14396
|
+
}
|
|
14397
|
+
async function copyPayload(path, layout, index, out) {
|
|
14398
|
+
const { payloadStart, payloadEnd } = layout;
|
|
14399
|
+
const payload = createReadStream4(path, { start: payloadStart, end: payloadEnd - 1 });
|
|
14400
|
+
await pipeline(payload, out, { end: false });
|
|
14401
|
+
if (payload.bytesRead !== payloadEnd - payloadStart) {
|
|
14402
|
+
throw new Error(
|
|
14403
|
+
`[mp4Stitch] chunk ${index} mdat holds ${payload.bytesRead} of ${payloadEnd - payloadStart} payload bytes`
|
|
14404
|
+
);
|
|
14405
|
+
}
|
|
14406
|
+
}
|
|
14407
|
+
async function writeStitchedMp4(chunkPaths, outputPath) {
|
|
14408
|
+
const layouts = await Promise.all(chunkPaths.map((path) => readMp4Layout(fileRange(path))));
|
|
14409
|
+
const header = buildStitchedHeader(layouts);
|
|
14410
|
+
mkdirSync10(dirname11(outputPath), { recursive: true });
|
|
14411
|
+
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
14412
|
+
const out = createWriteStream(join30(workDir, "out.mp4"));
|
|
14413
|
+
try {
|
|
14414
|
+
out.write(header);
|
|
14415
|
+
for (const [i, path] of chunkPaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
14416
|
+
out.end();
|
|
14417
|
+
await finished(out);
|
|
14418
|
+
renameSync4(join30(workDir, "out.mp4"), outputPath);
|
|
14419
|
+
} finally {
|
|
14420
|
+
out.destroy();
|
|
14421
|
+
rmSync9(workDir, { recursive: true, force: true });
|
|
14422
|
+
}
|
|
14423
|
+
}
|
|
14424
|
+
|
|
14425
|
+
// ../producer/src/services/distributed/chunkAudio.ts
|
|
14426
|
+
function audioCutForChunk(grid, chunks, index, fps) {
|
|
14427
|
+
const firstPacketAt = (frame) => Math.min(
|
|
14428
|
+
grid.packetCount,
|
|
14429
|
+
Math.max(
|
|
14430
|
+
0,
|
|
14431
|
+
Math.ceil(
|
|
14432
|
+
(frame * fps.den * grid.sampleRate - grid.firstStart * fps.num) / (grid.packetSamples * fps.num)
|
|
14433
|
+
)
|
|
14434
|
+
)
|
|
14435
|
+
);
|
|
14436
|
+
const first = index === 0 ? 0 : firstPacketAt(chunks[index].startFrame);
|
|
14437
|
+
const end = index === chunks.length - 1 ? grid.packetCount : firstPacketAt(chunks[index + 1].startFrame);
|
|
14438
|
+
if (end <= first) return null;
|
|
14439
|
+
const cutSeconds = (packet) => (grid.firstStart + packet * grid.packetSamples - grid.packetSamples / 2) / grid.sampleRate;
|
|
14440
|
+
const seekSeconds = index === 0 ? null : cutSeconds(first);
|
|
14441
|
+
const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
|
|
14442
|
+
return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
|
|
14443
|
+
}
|
|
14444
|
+
async function probeAacPacketGrid(audioPath) {
|
|
14445
|
+
const probe = await runFfprobeJson([
|
|
14446
|
+
"-v",
|
|
14447
|
+
"error",
|
|
14448
|
+
"-select_streams",
|
|
14449
|
+
"a:0",
|
|
14450
|
+
"-show_entries",
|
|
14451
|
+
"stream=sample_rate,time_base,nb_frames:packet=pts,duration",
|
|
14452
|
+
"-read_intervals",
|
|
14453
|
+
"%+#1",
|
|
14454
|
+
"-of",
|
|
14455
|
+
"json",
|
|
14456
|
+
"--",
|
|
14457
|
+
audioPath
|
|
14458
|
+
]);
|
|
14459
|
+
const stream = probe.streams?.[0] ?? {};
|
|
14460
|
+
const packet = probe.packets?.[0] ?? {};
|
|
14461
|
+
const sampleRate = Number(stream.sample_rate);
|
|
14462
|
+
const [tbNum = NaN, tbDen = NaN] = String(stream.time_base).split("/").map(Number);
|
|
14463
|
+
const toSamples = (ticks) => Number(ticks) * sampleRate * tbNum / tbDen;
|
|
14464
|
+
const grid = {
|
|
14465
|
+
sampleRate,
|
|
14466
|
+
firstStart: toSamples(packet.pts),
|
|
14467
|
+
packetSamples: toSamples(packet.duration),
|
|
14468
|
+
packetCount: Number(stream.nb_frames)
|
|
14469
|
+
};
|
|
14470
|
+
const counts = [grid.sampleRate, grid.packetSamples, grid.packetCount];
|
|
14471
|
+
if (!Number.isFinite(grid.firstStart) || !counts.every((n) => Number.isFinite(n) && n > 0)) {
|
|
14472
|
+
throw new Error(`[chunkAudio] could not read the AAC packet grid: ${JSON.stringify(probe)}`);
|
|
14473
|
+
}
|
|
14474
|
+
return grid;
|
|
14475
|
+
}
|
|
14476
|
+
var SLICE_DIR = "audio-chunks";
|
|
14477
|
+
var SLICE_FRAME = `${SLICE_DIR}/frame.m4a`;
|
|
14478
|
+
var SLICE_LAYOUT = `${SLICE_DIR}/layout.json`;
|
|
14479
|
+
function chunkAudioSlicePath(index) {
|
|
14480
|
+
return `${SLICE_DIR}/${String(index).padStart(4, "0")}.bin`;
|
|
14481
|
+
}
|
|
14482
|
+
function chunkAudioSliceTarget(path) {
|
|
14483
|
+
if (path === SLICE_FRAME || path === SLICE_LAYOUT) return "all";
|
|
14484
|
+
const match = /^audio-chunks\/(\d{4,})\.bin$/.exec(path);
|
|
14485
|
+
return match ? Number(match[1]) : null;
|
|
14486
|
+
}
|
|
14487
|
+
async function writeChunkAudioSlices(input) {
|
|
14488
|
+
const grid = await probeAacPacketGrid(input.audioPath);
|
|
14489
|
+
const { payloadStart, payloadEnd, moov } = await readMp4Layout(fileRange(input.audioPath));
|
|
14490
|
+
const [track] = readTracks(moov);
|
|
14491
|
+
const offsets = [];
|
|
14492
|
+
track.chunkOffsets.forEach((offset, chunk) => {
|
|
14493
|
+
for (let i = 0; i < track.samplesPerChunk[chunk]; i++) {
|
|
14494
|
+
offsets.push(offset);
|
|
14495
|
+
offset += track.sizes[offsets.length - 1];
|
|
14496
|
+
}
|
|
14497
|
+
});
|
|
14498
|
+
const bytes = readFileSync10(input.audioPath);
|
|
14499
|
+
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
14500
|
+
mkdirSync11(join31(input.planDir, SLICE_DIR), { recursive: true });
|
|
14501
|
+
input.chunks.forEach((_, index) => {
|
|
14502
|
+
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
14503
|
+
if (!cut) return;
|
|
14504
|
+
const last = cut.firstPacket + cut.packetCount - 1;
|
|
14505
|
+
const start = offsets[cut.firstPacket];
|
|
14506
|
+
layout.partStarts[index] = start;
|
|
14507
|
+
const end = offsets[last] + track.sizes[last];
|
|
14508
|
+
writeFileSync7(join31(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
14509
|
+
});
|
|
14510
|
+
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
14511
|
+
writeFileSync7(join31(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
14512
|
+
writeFileSync7(join31(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
14513
|
+
}
|
|
14514
|
+
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
14515
|
+
const layoutPath = join31(planDir, SLICE_LAYOUT);
|
|
14516
|
+
if (!existsSync19(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
14517
|
+
const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
|
|
14518
|
+
const frame = readFileSync10(join31(planDir, SLICE_FRAME));
|
|
14519
|
+
const fd = openSync5(scratchPath, "w");
|
|
14520
|
+
try {
|
|
14521
|
+
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
14522
|
+
writeSync(
|
|
14523
|
+
fd,
|
|
14524
|
+
frame,
|
|
14525
|
+
layout.payloadStart,
|
|
14526
|
+
frame.length - layout.payloadStart,
|
|
14527
|
+
layout.payloadEnd
|
|
14528
|
+
);
|
|
14529
|
+
const start = layout.partStarts[chunkIndex];
|
|
14530
|
+
if (start !== void 0) {
|
|
14531
|
+
const part = readFileSync10(join31(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
14532
|
+
writeSync(fd, part, 0, part.length, start);
|
|
14533
|
+
}
|
|
14534
|
+
ftruncateSync(fd, layout.sizeBytes);
|
|
14535
|
+
} finally {
|
|
14536
|
+
closeSync6(fd);
|
|
14537
|
+
}
|
|
14538
|
+
return scratchPath;
|
|
14539
|
+
}
|
|
14540
|
+
function chunkMuxArgs(input, cut) {
|
|
14541
|
+
if (!cut) return ["-i", input.videoPath, "-map", "0:v:0"];
|
|
14542
|
+
const seek = cut.seekSeconds === null ? [] : ["-ss", cut.seekSeconds.toFixed(6)];
|
|
14543
|
+
const length = cut.durationSeconds === null ? [] : ["-t", cut.durationSeconds.toFixed(6)];
|
|
14544
|
+
const exact = seek.length ? ["-copypriorss", "0"] : [];
|
|
14545
|
+
return [
|
|
14546
|
+
...["-i", input.videoPath, ...seek, ...length, "-i", input.audioPath],
|
|
14547
|
+
...["-map", "0:v:0", "-map", "1:a:0", ...exact]
|
|
14548
|
+
];
|
|
14549
|
+
}
|
|
14550
|
+
async function muxChunkWithAudio(input) {
|
|
14551
|
+
const grid = input.audioPath ? await probeAacPacketGrid(input.audioPath) : null;
|
|
14552
|
+
const cut = grid ? audioCutForChunk(grid, input.chunks, input.chunkIndex, input.fps) : null;
|
|
14553
|
+
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
14554
|
+
appendRenderProvenanceArgs(args, input.outputPath);
|
|
14555
|
+
args.push("-y", input.outputPath);
|
|
14556
|
+
const result = await runFfmpeg(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
14557
|
+
if (!result.success) {
|
|
14558
|
+
throw new Error(
|
|
14559
|
+
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure(result, input.timeoutMs)}`
|
|
14560
|
+
);
|
|
14561
|
+
}
|
|
14562
|
+
const slice = input.chunks[input.chunkIndex];
|
|
14563
|
+
const expected = { video: slice.endFrame - slice.startFrame };
|
|
14564
|
+
if (cut) expected.audio = cut.packetCount;
|
|
14565
|
+
const probe = await runFfprobeJson([
|
|
14566
|
+
"-v",
|
|
14567
|
+
"error",
|
|
14568
|
+
"-count_packets",
|
|
14569
|
+
"-show_entries",
|
|
14570
|
+
"stream=codec_type,nb_read_packets",
|
|
14571
|
+
"-of",
|
|
14572
|
+
"json",
|
|
14573
|
+
"--",
|
|
14574
|
+
input.outputPath
|
|
14575
|
+
]);
|
|
14576
|
+
for (const [type, count] of Object.entries(expected)) {
|
|
14577
|
+
const packets = Number(probe.streams?.find((s) => s.codec_type === type)?.nb_read_packets ?? 0);
|
|
14578
|
+
if (packets !== count) {
|
|
14579
|
+
throw new Error(`[chunkAudio] ${type} packets in chunk ${packets}, expected ${count}`);
|
|
14580
|
+
}
|
|
14581
|
+
}
|
|
14582
|
+
}
|
|
14583
|
+
|
|
14584
|
+
// ../producer/src/services/distributed/planSize.ts
|
|
14585
|
+
import { createHash as createHash6 } from "crypto";
|
|
14586
|
+
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "fs";
|
|
14587
|
+
import { extname as extname5, join as join32, relative as relative4 } from "path";
|
|
14588
|
+
var MAX_TOP_COMPONENTS = 10;
|
|
14589
|
+
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
14590
|
+
".aac",
|
|
14591
|
+
".aiff",
|
|
14592
|
+
".avi",
|
|
14593
|
+
".flac",
|
|
14594
|
+
".m4a",
|
|
14595
|
+
".m4v",
|
|
14596
|
+
".mkv",
|
|
14597
|
+
".mov",
|
|
14598
|
+
".mp3",
|
|
14599
|
+
".mp4",
|
|
14600
|
+
".mpeg",
|
|
14601
|
+
".mpg",
|
|
14602
|
+
".ogg",
|
|
14603
|
+
".ogv",
|
|
14604
|
+
".ts",
|
|
14605
|
+
".wav",
|
|
14606
|
+
".webm"
|
|
14607
|
+
]);
|
|
14608
|
+
var PLAN_ROOT_CATEGORY = {
|
|
14609
|
+
compiled: "compiled",
|
|
14610
|
+
[PLAN_AUDIO_RELATIVE_PATH]: "audio",
|
|
14611
|
+
meta: "metadata",
|
|
14612
|
+
"plan.json": "metadata"
|
|
14613
|
+
};
|
|
14614
|
+
function hashComponent(value) {
|
|
14615
|
+
return createHash6("sha256").update(value).digest("hex").slice(0, 12);
|
|
14616
|
+
}
|
|
14617
|
+
function classifyFile(segments, rootKind) {
|
|
14618
|
+
const rootName = segments[0] ?? "";
|
|
14619
|
+
const extractedRootName = rootKind === "compiled" ? "__hyperframes_video_frames" : "video-frames";
|
|
14620
|
+
if (rootName === extractedRootName) {
|
|
14621
|
+
const videoKey = segments[1] ?? "unknown";
|
|
14622
|
+
return {
|
|
14623
|
+
category: "video-frames",
|
|
14624
|
+
componentLabel: `video-frames:${hashComponent(videoKey)}`
|
|
14625
|
+
};
|
|
14626
|
+
}
|
|
14627
|
+
const category = rootKind === "compiled" ? "compiled" : PLAN_ROOT_CATEGORY[rootName] ?? "other";
|
|
14628
|
+
return { category, componentLabel: category };
|
|
14629
|
+
}
|
|
14630
|
+
function emptyBreakdown() {
|
|
14631
|
+
return {
|
|
14632
|
+
totalBytes: 0,
|
|
14633
|
+
fileCount: 0,
|
|
14634
|
+
compiledBytes: 0,
|
|
14635
|
+
sourceMediaBytes: 0,
|
|
14636
|
+
videoFramesBytes: 0,
|
|
14637
|
+
videoFrameFileCount: 0,
|
|
14638
|
+
audioBytes: 0,
|
|
14639
|
+
metadataBytes: 0,
|
|
14640
|
+
otherBytes: 0,
|
|
14641
|
+
topComponents: []
|
|
14642
|
+
};
|
|
14643
|
+
}
|
|
14644
|
+
function addCategoryBytes(result, category, filePath, sizeBytes) {
|
|
14645
|
+
if (category === "compiled") {
|
|
14646
|
+
result.compiledBytes += sizeBytes;
|
|
14647
|
+
if (SOURCE_MEDIA_EXTENSIONS.has(extname5(filePath).toLowerCase())) {
|
|
14648
|
+
result.sourceMediaBytes += sizeBytes;
|
|
14649
|
+
}
|
|
14650
|
+
return;
|
|
14651
|
+
}
|
|
14652
|
+
if (category === "video-frames") {
|
|
14653
|
+
result.videoFramesBytes += sizeBytes;
|
|
14654
|
+
result.videoFrameFileCount += 1;
|
|
14655
|
+
return;
|
|
14656
|
+
}
|
|
14657
|
+
if (category === "audio") {
|
|
14658
|
+
result.audioBytes += sizeBytes;
|
|
14659
|
+
return;
|
|
14660
|
+
}
|
|
14661
|
+
if (category === "metadata") {
|
|
14662
|
+
result.metadataBytes += sizeBytes;
|
|
14663
|
+
return;
|
|
14664
|
+
}
|
|
14665
|
+
result.otherBytes += sizeBytes;
|
|
14666
|
+
}
|
|
14667
|
+
function recordFile(result, components, rootDir, rootKind, filePath) {
|
|
14668
|
+
let sizeBytes;
|
|
14669
|
+
try {
|
|
14670
|
+
const stat = lstatSync2(filePath);
|
|
14671
|
+
if (!stat.isFile()) return;
|
|
14672
|
+
sizeBytes = stat.size;
|
|
14673
|
+
} catch {
|
|
14674
|
+
return;
|
|
14675
|
+
}
|
|
14676
|
+
const rel = relative4(rootDir, filePath);
|
|
14677
|
+
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
14678
|
+
const classified = classifyFile(segments, rootKind);
|
|
14679
|
+
result.totalBytes += sizeBytes;
|
|
14680
|
+
result.fileCount += 1;
|
|
14681
|
+
addCategoryBytes(result, classified.category, filePath, sizeBytes);
|
|
14682
|
+
const component = components.get(classified.componentLabel) ?? {
|
|
14683
|
+
label: classified.componentLabel,
|
|
14684
|
+
sizeBytes: 0,
|
|
14685
|
+
fileCount: 0
|
|
14686
|
+
};
|
|
14687
|
+
component.sizeBytes += sizeBytes;
|
|
14688
|
+
component.fileCount += 1;
|
|
14689
|
+
components.set(classified.componentLabel, component);
|
|
14690
|
+
}
|
|
14691
|
+
function walkRegularFiles(dir, visit) {
|
|
14692
|
+
let entries;
|
|
14693
|
+
try {
|
|
14694
|
+
entries = readdirSync8(dir, { withFileTypes: true });
|
|
14695
|
+
} catch {
|
|
14696
|
+
return;
|
|
14697
|
+
}
|
|
14698
|
+
for (const entry of entries) {
|
|
14699
|
+
const full = join32(dir, entry.name);
|
|
14700
|
+
if (entry.isDirectory()) {
|
|
14701
|
+
walkRegularFiles(full, visit);
|
|
14702
|
+
} else if (entry.isFile()) {
|
|
14703
|
+
visit(full);
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
}
|
|
14707
|
+
function measurePlanSizeBreakdown(rootDir, rootKind = "plan") {
|
|
14708
|
+
const result = emptyBreakdown();
|
|
14709
|
+
const components = /* @__PURE__ */ new Map();
|
|
14710
|
+
walkRegularFiles(
|
|
14711
|
+
rootDir,
|
|
14712
|
+
(filePath) => recordFile(result, components, rootDir, rootKind, filePath)
|
|
14713
|
+
);
|
|
14714
|
+
result.topComponents = [...components.values()].sort((a, b) => b.sizeBytes - a.sizeBytes || a.label.localeCompare(b.label)).slice(0, MAX_TOP_COMPONENTS);
|
|
14715
|
+
return result;
|
|
14716
|
+
}
|
|
14717
|
+
|
|
14718
|
+
// ../producer/src/services/distributed/plan.ts
|
|
14719
|
+
function logPlanStage(log, stage, startedAt) {
|
|
14720
|
+
log.info("[plan] stage", { stage, ms: Date.now() - startedAt });
|
|
14721
|
+
}
|
|
14722
|
+
function applyDistributedAudioWarningPolicy(job, audioError, audioFailures = [], log = defaultLogger) {
|
|
14723
|
+
const failureOwner = audioFailures.length === 0 ? void 0 : audioFailures.some((failure) => failure.owner === "system") ? "system" : "user";
|
|
14724
|
+
const retryable = audioFailures.length === 0 ? void 0 : audioFailures.every((failure) => failure.retryable);
|
|
14725
|
+
applyRenderWarningPolicy(
|
|
14726
|
+
job,
|
|
14727
|
+
[
|
|
14728
|
+
{
|
|
14729
|
+
code: "audio_processing_failed",
|
|
14730
|
+
message: `Audio mix failed; output would be video-only: ${audioError}`,
|
|
14731
|
+
details: {
|
|
14732
|
+
mediaType: "audio",
|
|
14733
|
+
audioFailures: audioFailureDiagnostics(audioFailures),
|
|
14734
|
+
failureReasons: [...new Set(audioFailures.map((failure) => failure.reason))],
|
|
14735
|
+
failureStages: [...new Set(audioFailures.map((failure) => failure.stage))],
|
|
14736
|
+
failureOwner,
|
|
14737
|
+
retryable
|
|
14738
|
+
}
|
|
14739
|
+
}
|
|
14740
|
+
],
|
|
14741
|
+
log
|
|
14742
|
+
);
|
|
14743
|
+
}
|
|
14744
|
+
var PLAN_PROJECT_DIR_SKIP_SEGMENTS = /* @__PURE__ */ new Set([
|
|
14745
|
+
"node_modules",
|
|
14746
|
+
".git",
|
|
14747
|
+
".cache",
|
|
14197
14748
|
"output",
|
|
14198
14749
|
"failures",
|
|
14199
14750
|
"dist",
|
|
@@ -14355,13 +14906,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
14355
14906
|
}
|
|
14356
14907
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
14357
14908
|
"plan.json",
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14909
|
+
join33("meta", "composition.json"),
|
|
14910
|
+
join33("meta", "encoder.json"),
|
|
14911
|
+
join33("meta", "chunks.json")
|
|
14361
14912
|
];
|
|
14362
14913
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
14363
14914
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
14364
|
-
|
|
14915
|
+
rmSync10(join33(planDir, relativePath), { force: true });
|
|
14365
14916
|
}
|
|
14366
14917
|
}
|
|
14367
14918
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -14492,19 +15043,19 @@ function createVideoSourceShipper(input) {
|
|
|
14492
15043
|
const known = shipped.get(srcPath);
|
|
14493
15044
|
if (known) return known;
|
|
14494
15045
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
14495
|
-
const current = stagedRel === null ? srcPath :
|
|
15046
|
+
const current = stagedRel === null ? srcPath : join33(input.planDir, "video-frames", stagedRel);
|
|
14496
15047
|
let result = null;
|
|
14497
15048
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
14498
15049
|
const rel = pathInside(root, srcPath);
|
|
14499
|
-
if (rel !== null &&
|
|
15050
|
+
if (rel !== null && (existsSync20(join33(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
14500
15051
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
14501
15052
|
break;
|
|
14502
15053
|
}
|
|
14503
15054
|
}
|
|
14504
15055
|
if (result === null) {
|
|
14505
15056
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
14506
|
-
const destination =
|
|
14507
|
-
|
|
15057
|
+
const destination = join33(input.planDir, ...result.split("/"));
|
|
15058
|
+
mkdirSync12(join33(input.planDir, "video-sources"), { recursive: true });
|
|
14508
15059
|
try {
|
|
14509
15060
|
linkSync(current, destination);
|
|
14510
15061
|
} catch {
|
|
@@ -14522,7 +15073,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14522
15073
|
useGpu: false,
|
|
14523
15074
|
browserGpuMode: "software"
|
|
14524
15075
|
});
|
|
14525
|
-
if (!
|
|
15076
|
+
if (!existsSync20(planDir)) mkdirSync12(planDir, { recursive: true });
|
|
14526
15077
|
const log = config.logger ?? defaultLogger;
|
|
14527
15078
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
14528
15079
|
const abortSignal = config.abortSignal;
|
|
@@ -14551,15 +15102,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14551
15102
|
variables: config.variables
|
|
14552
15103
|
});
|
|
14553
15104
|
const entryFile = config.entryFile ?? "index.html";
|
|
14554
|
-
const htmlPath =
|
|
14555
|
-
if (!
|
|
15105
|
+
const htmlPath = join33(projectDir, entryFile);
|
|
15106
|
+
if (!existsSync20(htmlPath)) {
|
|
14556
15107
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
14557
15108
|
}
|
|
14558
|
-
const workDir =
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
const compiledDir =
|
|
14562
|
-
|
|
15109
|
+
const workDir = join33(planDir, ".plan-work");
|
|
15110
|
+
rmSync10(workDir, { recursive: true, force: true });
|
|
15111
|
+
mkdirSync12(workDir, { recursive: true });
|
|
15112
|
+
const compiledDir = join33(workDir, "compiled");
|
|
15113
|
+
mkdirSync12(compiledDir, { recursive: true });
|
|
14563
15114
|
let stageStarted = Date.now();
|
|
14564
15115
|
cpSync2(projectDir, compiledDir, {
|
|
14565
15116
|
recursive: true,
|
|
@@ -14572,10 +15123,19 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14572
15123
|
}
|
|
14573
15124
|
});
|
|
14574
15125
|
logPlanStage(log, "copy", stageStarted);
|
|
14575
|
-
const finalCompiledDir =
|
|
15126
|
+
const finalCompiledDir = join33(planDir, "compiled");
|
|
14576
15127
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
15128
|
+
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
15129
|
+
const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
|
|
15130
|
+
const urlByAbsentLocalPath = new Map(
|
|
15131
|
+
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
15132
|
+
);
|
|
15133
|
+
const localPathByUrl = new Map(
|
|
15134
|
+
remoteFiles.map(([rel, file]) => [file.url, remoteLocalPath(rel)])
|
|
15135
|
+
);
|
|
14577
15136
|
stageStarted = Date.now();
|
|
14578
15137
|
const compileResult = await runCompileStage({
|
|
15138
|
+
urlByAbsentLocalPath,
|
|
14579
15139
|
projectDir,
|
|
14580
15140
|
workDir,
|
|
14581
15141
|
htmlPath,
|
|
@@ -14635,7 +15195,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14635
15195
|
if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
|
|
14636
15196
|
if (probeResult.probeSession) {
|
|
14637
15197
|
try {
|
|
14638
|
-
const { closeCaptureSession: closeCaptureSession2 } = await import("./src-
|
|
15198
|
+
const { closeCaptureSession: closeCaptureSession2 } = await import("./src-XRFOS5LP.js");
|
|
14639
15199
|
await closeCaptureSession2(probeResult.probeSession);
|
|
14640
15200
|
} catch (err) {
|
|
14641
15201
|
log.warn("[plan] probe session close failed", {
|
|
@@ -14663,30 +15223,32 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14663
15223
|
abortSignal,
|
|
14664
15224
|
assertNotAborted,
|
|
14665
15225
|
materializeSymlinks: true,
|
|
14666
|
-
deferRangeExtraction: options.deferVideoExtraction
|
|
15226
|
+
deferRangeExtraction: options.deferVideoExtraction,
|
|
15227
|
+
urlByAbsentLocalPath
|
|
14667
15228
|
});
|
|
14668
15229
|
logPlanStage(log, "extract", stageStarted);
|
|
14669
15230
|
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
14670
15231
|
if (extractResult.extractionResult) {
|
|
14671
15232
|
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
14672
15233
|
}
|
|
14673
|
-
const stagedVideoFrames =
|
|
14674
|
-
const videoFramesDst =
|
|
14675
|
-
if (
|
|
14676
|
-
const videoSourcesDir =
|
|
14677
|
-
|
|
14678
|
-
if (
|
|
14679
|
-
|
|
15234
|
+
const stagedVideoFrames = join33(compiledDir, "__hyperframes_video_frames");
|
|
15235
|
+
const videoFramesDst = join33(planDir, "video-frames");
|
|
15236
|
+
if (existsSync20(videoFramesDst)) rmSync10(videoFramesDst, { recursive: true, force: true });
|
|
15237
|
+
const videoSourcesDir = join33(planDir, "video-sources");
|
|
15238
|
+
rmSync10(videoSourcesDir, { recursive: true, force: true });
|
|
15239
|
+
if (existsSync20(stagedVideoFrames)) {
|
|
15240
|
+
renameSync5(stagedVideoFrames, videoFramesDst);
|
|
14680
15241
|
} else {
|
|
14681
|
-
|
|
15242
|
+
mkdirSync12(videoFramesDst, { recursive: true });
|
|
14682
15243
|
}
|
|
14683
|
-
if (
|
|
14684
|
-
|
|
15244
|
+
if (existsSync20(finalCompiledDir)) rmSync10(finalCompiledDir, { recursive: true, force: true });
|
|
15245
|
+
renameSync5(compiledDir, finalCompiledDir);
|
|
14685
15246
|
const shipVideoSource = createVideoSourceShipper({
|
|
14686
15247
|
planDir,
|
|
14687
15248
|
projectDir,
|
|
14688
15249
|
workCompiledDir: compiledDir,
|
|
14689
|
-
workVideoFramesDir: stagedVideoFrames
|
|
15250
|
+
workVideoFramesDir: stagedVideoFrames,
|
|
15251
|
+
isRemote: (srcPath) => urlByAbsentLocalPath.has(srcPath)
|
|
14690
15252
|
});
|
|
14691
15253
|
const sourceRoots = [
|
|
14692
15254
|
[stagedVideoFrames, "video-frames"],
|
|
@@ -14696,19 +15258,30 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14696
15258
|
const planVideosJson = buildPlanVideosJson({
|
|
14697
15259
|
videos: composition.videos,
|
|
14698
15260
|
compositionEnd: job.duration ?? Number.NaN,
|
|
14699
|
-
extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) =>
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
15261
|
+
extracted: (extractResult.extractionResult?.extracted ?? []).map((ext) => {
|
|
15262
|
+
const sourcePath = localPathByUrl.get(ext.srcPath) ?? ext.srcPath;
|
|
15263
|
+
return {
|
|
15264
|
+
videoId: ext.videoId,
|
|
15265
|
+
srcPath: stableSourcePath(sourceRoots, sourcePath),
|
|
15266
|
+
framePattern: ext.framePattern,
|
|
15267
|
+
fps: ext.fps,
|
|
15268
|
+
totalFrames: ext.totalFrames,
|
|
15269
|
+
metadata: ext.metadata,
|
|
15270
|
+
...ext.deferredRange ? { deferredRange: { ...ext.deferredRange, sourcePath: shipVideoSource(sourcePath) } } : {}
|
|
15271
|
+
};
|
|
15272
|
+
})
|
|
14708
15273
|
});
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
15274
|
+
mkdirSync12(join33(planDir, "meta"), { recursive: true });
|
|
15275
|
+
writePlanRemoteFiles(
|
|
15276
|
+
planDir,
|
|
15277
|
+
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
15278
|
+
path: `compiled/${rel}`,
|
|
15279
|
+
sha256,
|
|
15280
|
+
sizeBytes
|
|
15281
|
+
}))
|
|
15282
|
+
);
|
|
15283
|
+
writeFileSync8(
|
|
15284
|
+
join33(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
14712
15285
|
JSON.stringify(planVideosJson, null, 2),
|
|
14713
15286
|
"utf-8"
|
|
14714
15287
|
);
|
|
@@ -14721,6 +15294,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14721
15294
|
projectDir,
|
|
14722
15295
|
workDir,
|
|
14723
15296
|
compiledDir: finalCompiledDir,
|
|
15297
|
+
urlByAbsentLocalPath,
|
|
14724
15298
|
duration: audioSeconds,
|
|
14725
15299
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
14726
15300
|
audioGain: cfg.audioGain,
|
|
@@ -14781,11 +15355,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14781
15355
|
if (audioResult.audioError) {
|
|
14782
15356
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
14783
15357
|
}
|
|
14784
|
-
const planAudioPath =
|
|
14785
|
-
if (audioResult.hasAudio &&
|
|
14786
|
-
|
|
15358
|
+
const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
15359
|
+
if (audioResult.hasAudio && existsSync20(audioResult.audioOutputPath)) {
|
|
15360
|
+
renameSync5(audioResult.audioOutputPath, planAudioPath);
|
|
14787
15361
|
}
|
|
14788
|
-
if (chunksCarryAudio2 &&
|
|
15362
|
+
if (chunksCarryAudio2 && existsSync20(planAudioPath)) {
|
|
14789
15363
|
stageStarted = Date.now();
|
|
14790
15364
|
const peak = await constrainAacTruePeak({
|
|
14791
15365
|
audioPath: planAudioPath,
|
|
@@ -14795,6 +15369,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14795
15369
|
});
|
|
14796
15370
|
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
14797
15371
|
logPlanStage(log, "audio-peak", stageStarted);
|
|
15372
|
+
if (options.sliceChunkAudio) {
|
|
15373
|
+
stageStarted = Date.now();
|
|
15374
|
+
await writeChunkAudioSlices({
|
|
15375
|
+
audioPath: planAudioPath,
|
|
15376
|
+
planDir,
|
|
15377
|
+
chunks,
|
|
15378
|
+
fps: { num: config.fps, den: 1 }
|
|
15379
|
+
});
|
|
15380
|
+
logPlanStage(log, "audio-slices", stageStarted);
|
|
15381
|
+
}
|
|
14798
15382
|
}
|
|
14799
15383
|
const ffmpegVersion = await readFfmpegVersion();
|
|
14800
15384
|
const producerVersion = readProducerVersion();
|
|
@@ -14828,7 +15412,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14828
15412
|
format: config.format
|
|
14829
15413
|
};
|
|
14830
15414
|
try {
|
|
14831
|
-
|
|
15415
|
+
rmSync10(workDir, { recursive: true, force: true });
|
|
14832
15416
|
} catch (err) {
|
|
14833
15417
|
log.warn("[plan] failed to remove temp work dir", {
|
|
14834
15418
|
workDir,
|
|
@@ -15370,15 +15954,15 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
15370
15954
|
|
|
15371
15955
|
// ../producer/src/server.ts
|
|
15372
15956
|
import {
|
|
15373
|
-
existsSync as
|
|
15374
|
-
mkdirSync as
|
|
15957
|
+
existsSync as existsSync23,
|
|
15958
|
+
mkdirSync as mkdirSync13,
|
|
15375
15959
|
statSync as statSync7,
|
|
15376
|
-
mkdtempSync as
|
|
15377
|
-
writeFileSync as
|
|
15378
|
-
rmSync as
|
|
15379
|
-
createReadStream as
|
|
15960
|
+
mkdtempSync as mkdtempSync5,
|
|
15961
|
+
writeFileSync as writeFileSync9,
|
|
15962
|
+
rmSync as rmSync11,
|
|
15963
|
+
createReadStream as createReadStream5
|
|
15380
15964
|
} from "fs";
|
|
15381
|
-
import { resolve as resolve9, dirname as
|
|
15965
|
+
import { resolve as resolve9, dirname as dirname13, join as join36 } from "path";
|
|
15382
15966
|
import { tmpdir as tmpdir3 } from "os";
|
|
15383
15967
|
import { parseArgs } from "util";
|
|
15384
15968
|
import crypto from "crypto";
|
|
@@ -15388,15 +15972,15 @@ import { serve as serve2 } from "@hono/node-server";
|
|
|
15388
15972
|
|
|
15389
15973
|
// ../producer/src/services/hyperframeLint.ts
|
|
15390
15974
|
import {
|
|
15391
|
-
existsSync as
|
|
15392
|
-
readFileSync as
|
|
15975
|
+
existsSync as existsSync21,
|
|
15976
|
+
readFileSync as readFileSync11,
|
|
15393
15977
|
statSync as statSync6,
|
|
15394
|
-
openSync as
|
|
15978
|
+
openSync as openSync6,
|
|
15395
15979
|
fstatSync as fstatSync4,
|
|
15396
|
-
closeSync as
|
|
15980
|
+
closeSync as closeSync7,
|
|
15397
15981
|
constants as constants3
|
|
15398
15982
|
} from "fs";
|
|
15399
|
-
import { resolve as resolve8, join as
|
|
15983
|
+
import { resolve as resolve8, join as join34, relative as relative6, isAbsolute as isAbsolute3, sep as sep3 } from "path";
|
|
15400
15984
|
function isStringRecord(value) {
|
|
15401
15985
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
15402
15986
|
return false;
|
|
@@ -15425,7 +16009,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
15425
16009
|
function readEntryHtml(filePath) {
|
|
15426
16010
|
let fd;
|
|
15427
16011
|
try {
|
|
15428
|
-
fd =
|
|
16012
|
+
fd = openSync6(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
|
|
15429
16013
|
} catch (error) {
|
|
15430
16014
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
15431
16015
|
return null;
|
|
@@ -15434,14 +16018,14 @@ function readEntryHtml(filePath) {
|
|
|
15434
16018
|
}
|
|
15435
16019
|
try {
|
|
15436
16020
|
if (!fstatSync4(fd).isFile()) return null;
|
|
15437
|
-
return
|
|
16021
|
+
return readFileSync11(fd, "utf-8");
|
|
15438
16022
|
} finally {
|
|
15439
|
-
|
|
16023
|
+
closeSync7(fd);
|
|
15440
16024
|
}
|
|
15441
16025
|
}
|
|
15442
16026
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15443
16027
|
const absProjectDir = resolve8(projectDir);
|
|
15444
|
-
if (!
|
|
16028
|
+
if (!existsSync21(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
|
|
15445
16029
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15446
16030
|
}
|
|
15447
16031
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15463,7 +16047,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
15463
16047
|
}
|
|
15464
16048
|
}
|
|
15465
16049
|
return {
|
|
15466
|
-
error: `No HTML entry file found in project directory: ${
|
|
16050
|
+
error: `No HTML entry file found in project directory: ${join34(absProjectDir, preferredEntryFile || "index.html")}`
|
|
15467
16051
|
};
|
|
15468
16052
|
}
|
|
15469
16053
|
function prepareHyperframeLintBody(body) {
|
|
@@ -15509,8 +16093,8 @@ async function runHyperframeLint(prepared) {
|
|
|
15509
16093
|
// ../producer/src/services/healthWorker.ts
|
|
15510
16094
|
import { Worker as Worker2 } from "worker_threads";
|
|
15511
16095
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
15512
|
-
import { dirname as
|
|
15513
|
-
import { existsSync as
|
|
16096
|
+
import { dirname as dirname12, join as join35 } from "path";
|
|
16097
|
+
import { existsSync as existsSync22 } from "fs";
|
|
15514
16098
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15515
16099
|
async function startHealthWorker(options = {}) {
|
|
15516
16100
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -15584,10 +16168,10 @@ function defaultLogger2() {
|
|
|
15584
16168
|
};
|
|
15585
16169
|
}
|
|
15586
16170
|
function resolveWorkerEntry2() {
|
|
15587
|
-
const here =
|
|
15588
|
-
const candidates = [
|
|
16171
|
+
const here = dirname12(fileURLToPath6(import.meta.url));
|
|
16172
|
+
const candidates = [join35(here, "healthWorkerThread.js"), join35(here, "healthWorkerThread.ts")];
|
|
15589
16173
|
for (const candidate of candidates) {
|
|
15590
|
-
if (
|
|
16174
|
+
if (existsSync22(candidate)) return candidate;
|
|
15591
16175
|
}
|
|
15592
16176
|
return void 0;
|
|
15593
16177
|
}
|
|
@@ -15725,8 +16309,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
15725
16309
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
15726
16310
|
const { input, cleanupProjectDir } = prepared;
|
|
15727
16311
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
15728
|
-
const outputDir =
|
|
15729
|
-
if (!
|
|
16312
|
+
const outputDir = dirname13(absoluteOutputPath);
|
|
16313
|
+
if (!existsSync23(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
15730
16314
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
15731
16315
|
}
|
|
15732
16316
|
function validateRenderOverrides(body) {
|
|
@@ -15766,11 +16350,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
15766
16350
|
const candidate = nonEmptyString(projectDir);
|
|
15767
16351
|
if (!candidate) return null;
|
|
15768
16352
|
const absProjectDir = resolve9(candidate);
|
|
15769
|
-
if (!
|
|
16353
|
+
if (!existsSync23(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
|
|
15770
16354
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15771
16355
|
}
|
|
15772
16356
|
const entry = options.entryFile || "index.html";
|
|
15773
|
-
if (!
|
|
16357
|
+
if (!existsSync23(resolve9(absProjectDir, entry))) {
|
|
15774
16358
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
15775
16359
|
}
|
|
15776
16360
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -15795,8 +16379,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
15795
16379
|
}
|
|
15796
16380
|
function materializeInlineProject(html, options) {
|
|
15797
16381
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
15798
|
-
const tempProjectDir =
|
|
15799
|
-
|
|
16382
|
+
const tempProjectDir = mkdtempSync5(join36(tempRoot, "producer-project-"));
|
|
16383
|
+
writeFileSync9(join36(tempProjectDir, "index.html"), html, "utf-8");
|
|
15800
16384
|
return {
|
|
15801
16385
|
prepared: {
|
|
15802
16386
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -15853,7 +16437,7 @@ function createArtifactStore(ttlMs) {
|
|
|
15853
16437
|
function cleanupTempDir(dir, log) {
|
|
15854
16438
|
if (!dir) return;
|
|
15855
16439
|
try {
|
|
15856
|
-
|
|
16440
|
+
rmSync11(dir, { recursive: true, force: true });
|
|
15857
16441
|
} catch (error) {
|
|
15858
16442
|
log.warn("Failed to cleanup temp project dir", {
|
|
15859
16443
|
cleanupProjectDir: dir,
|
|
@@ -15862,7 +16446,7 @@ function cleanupTempDir(dir, log) {
|
|
|
15862
16446
|
}
|
|
15863
16447
|
}
|
|
15864
16448
|
function outputFileSize(path) {
|
|
15865
|
-
return
|
|
16449
|
+
return existsSync23(path) ? statSync7(path).size : 0;
|
|
15866
16450
|
}
|
|
15867
16451
|
function createBlockingProgressReporter(log, requestId) {
|
|
15868
16452
|
let lastLoggedPct = -10;
|
|
@@ -16158,12 +16742,12 @@ function createRenderHandlers(options = {}) {
|
|
|
16158
16742
|
if (!artifact) {
|
|
16159
16743
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
16160
16744
|
}
|
|
16161
|
-
if (!
|
|
16745
|
+
if (!existsSync23(artifact.path)) {
|
|
16162
16746
|
store.delete(token);
|
|
16163
16747
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16164
16748
|
}
|
|
16165
16749
|
const stats = statSync7(artifact.path);
|
|
16166
|
-
return new Response(
|
|
16750
|
+
return new Response(createReadStream5(artifact.path), {
|
|
16167
16751
|
headers: {
|
|
16168
16752
|
"content-type": "video/mp4",
|
|
16169
16753
|
"content-length": String(stats.size),
|
|
@@ -16205,7 +16789,7 @@ function startServer(options = {}) {
|
|
|
16205
16789
|
});
|
|
16206
16790
|
async function shutdown(signal) {
|
|
16207
16791
|
log.info(`Received ${signal}, shutting down`);
|
|
16208
|
-
const { drainBrowserPool } = await import("./src-
|
|
16792
|
+
const { drainBrowserPool } = await import("./src-XRFOS5LP.js");
|
|
16209
16793
|
await drainBrowserPool().catch(() => {
|
|
16210
16794
|
});
|
|
16211
16795
|
const handle = await Promise.race([
|
|
@@ -16243,33 +16827,33 @@ if (isPublicServerEntry) {
|
|
|
16243
16827
|
// ../producer/src/services/distributed/planV2.ts
|
|
16244
16828
|
import {
|
|
16245
16829
|
copyFileSync as copyFileSync6,
|
|
16246
|
-
existsSync as
|
|
16830
|
+
existsSync as existsSync25,
|
|
16247
16831
|
lstatSync as lstatSync3,
|
|
16248
|
-
mkdirSync as
|
|
16249
|
-
mkdtempSync as
|
|
16250
|
-
readFileSync as
|
|
16832
|
+
mkdirSync as mkdirSync15,
|
|
16833
|
+
mkdtempSync as mkdtempSync7,
|
|
16834
|
+
readFileSync as readFileSync12,
|
|
16251
16835
|
readdirSync as readdirSync9,
|
|
16252
|
-
renameSync as
|
|
16253
|
-
rmSync as
|
|
16836
|
+
renameSync as renameSync7,
|
|
16837
|
+
rmSync as rmSync13,
|
|
16254
16838
|
statSync as statSync9,
|
|
16255
|
-
writeFileSync as
|
|
16839
|
+
writeFileSync as writeFileSync11
|
|
16256
16840
|
} from "fs";
|
|
16257
16841
|
import { tmpdir as tmpdir4 } from "os";
|
|
16258
|
-
import { dirname as
|
|
16842
|
+
import { dirname as dirname15, isAbsolute as isAbsolute4, join as join39, relative as relative7, resolve as resolve10, sep as sep4 } from "path";
|
|
16259
16843
|
|
|
16260
16844
|
// ../producer/src/services/distributed/planV2Publisher.ts
|
|
16261
16845
|
import {
|
|
16262
16846
|
copyFileSync as copyFileSync5,
|
|
16263
|
-
existsSync as
|
|
16847
|
+
existsSync as existsSync24,
|
|
16264
16848
|
linkSync as linkSync2,
|
|
16265
|
-
mkdirSync as
|
|
16266
|
-
mkdtempSync as
|
|
16267
|
-
renameSync as
|
|
16268
|
-
rmSync as
|
|
16849
|
+
mkdirSync as mkdirSync14,
|
|
16850
|
+
mkdtempSync as mkdtempSync6,
|
|
16851
|
+
renameSync as renameSync6,
|
|
16852
|
+
rmSync as rmSync12,
|
|
16269
16853
|
statSync as statSync8,
|
|
16270
|
-
writeFileSync as
|
|
16854
|
+
writeFileSync as writeFileSync10
|
|
16271
16855
|
} from "fs";
|
|
16272
|
-
import { dirname as
|
|
16856
|
+
import { dirname as dirname14, join as join38 } from "path";
|
|
16273
16857
|
|
|
16274
16858
|
// ../producer/src/services/distributed/planV2Errors.ts
|
|
16275
16859
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -16284,7 +16868,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
16284
16868
|
};
|
|
16285
16869
|
|
|
16286
16870
|
// ../producer/src/services/distributed/planV2Layout.ts
|
|
16287
|
-
import { join as
|
|
16871
|
+
import { join as join37 } from "path";
|
|
16288
16872
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
16289
16873
|
function assertPlanV2Sha256(value, field) {
|
|
16290
16874
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -16294,7 +16878,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
16294
16878
|
}
|
|
16295
16879
|
function planV2BlobPath(root, sha256) {
|
|
16296
16880
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
16297
|
-
return
|
|
16881
|
+
return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
16298
16882
|
}
|
|
16299
16883
|
|
|
16300
16884
|
// ../producer/src/services/distributed/planV2Publisher.ts
|
|
@@ -16329,13 +16913,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16329
16913
|
#linkFile;
|
|
16330
16914
|
#committed = false;
|
|
16331
16915
|
constructor(destinationDir, options = {}) {
|
|
16332
|
-
if (
|
|
16916
|
+
if (existsSync24(destinationDir)) {
|
|
16333
16917
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
16334
16918
|
}
|
|
16335
16919
|
this.destinationDir = destinationDir;
|
|
16336
16920
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
16337
|
-
|
|
16338
|
-
this.temporaryDir =
|
|
16921
|
+
mkdirSync14(dirname14(destinationDir), { recursive: true });
|
|
16922
|
+
this.temporaryDir = mkdtempSync6(join38(dirname14(destinationDir), ".plan-v2-publish-"));
|
|
16339
16923
|
}
|
|
16340
16924
|
async putBlob(blob) {
|
|
16341
16925
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -16346,10 +16930,10 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16346
16930
|
);
|
|
16347
16931
|
}
|
|
16348
16932
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
16349
|
-
if (
|
|
16350
|
-
|
|
16351
|
-
const stagingDir =
|
|
16352
|
-
const temporaryPath =
|
|
16933
|
+
if (existsSync24(destinationPath)) return;
|
|
16934
|
+
mkdirSync14(dirname14(destinationPath), { recursive: true });
|
|
16935
|
+
const stagingDir = mkdtempSync6(join38(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
16936
|
+
const temporaryPath = join38(stagingDir, "blob");
|
|
16353
16937
|
try {
|
|
16354
16938
|
try {
|
|
16355
16939
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -16357,26 +16941,26 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16357
16941
|
if (!canFallbackToCopy(error)) throw error;
|
|
16358
16942
|
copyFileSync5(blob.sourcePath, temporaryPath);
|
|
16359
16943
|
}
|
|
16360
|
-
|
|
16944
|
+
renameSync6(temporaryPath, destinationPath);
|
|
16361
16945
|
} finally {
|
|
16362
|
-
|
|
16946
|
+
rmSync12(stagingDir, { recursive: true, force: true });
|
|
16363
16947
|
}
|
|
16364
16948
|
}
|
|
16365
16949
|
async commitManifest(manifestBytes) {
|
|
16366
16950
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
16367
|
-
if (!
|
|
16951
|
+
if (!existsSync24(planV2BlobPath(this.temporaryDir, digest))) {
|
|
16368
16952
|
throw new PlanV2IntegrityError(
|
|
16369
16953
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
16370
16954
|
);
|
|
16371
16955
|
}
|
|
16372
16956
|
}
|
|
16373
|
-
|
|
16374
|
-
|
|
16957
|
+
writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
16958
|
+
renameSync6(this.temporaryDir, this.destinationDir);
|
|
16375
16959
|
this.#committed = true;
|
|
16376
16960
|
}
|
|
16377
16961
|
async abort() {
|
|
16378
16962
|
if (!this.#committed) {
|
|
16379
|
-
|
|
16963
|
+
rmSync12(this.temporaryDir, { recursive: true, force: true });
|
|
16380
16964
|
}
|
|
16381
16965
|
}
|
|
16382
16966
|
};
|
|
@@ -16395,7 +16979,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
16395
16979
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
16396
16980
|
}
|
|
16397
16981
|
function readJsonFile(path, label) {
|
|
16398
|
-
const contents =
|
|
16982
|
+
const contents = readFileSync12(path, "utf-8");
|
|
16399
16983
|
try {
|
|
16400
16984
|
const parsed = JSON.parse(contents);
|
|
16401
16985
|
return parsed;
|
|
@@ -16415,7 +16999,7 @@ function listFiles(root) {
|
|
|
16415
16999
|
const rootResolved = resolve10(root);
|
|
16416
17000
|
function walk(dir) {
|
|
16417
17001
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
16418
|
-
const absolutePath =
|
|
17002
|
+
const absolutePath = join39(dir, entry.name);
|
|
16419
17003
|
if (entry.isDirectory()) {
|
|
16420
17004
|
walk(absolutePath);
|
|
16421
17005
|
} else if (entry.isFile()) {
|
|
@@ -16442,13 +17026,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
16442
17026
|
}
|
|
16443
17027
|
}
|
|
16444
17028
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
16445
|
-
const videoRoot =
|
|
16446
|
-
if (!
|
|
17029
|
+
const videoRoot = join39(executionPlanDir, "video-frames");
|
|
17030
|
+
if (!existsSync25(videoRoot)) return;
|
|
16447
17031
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
16448
17032
|
if (!videoEntry.isDirectory()) continue;
|
|
16449
|
-
const videoDir =
|
|
17033
|
+
const videoDir = join39(videoRoot, videoEntry.name);
|
|
16450
17034
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
16451
|
-
const sentinelPath =
|
|
17035
|
+
const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
16452
17036
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
16453
17037
|
}
|
|
16454
17038
|
}
|
|
@@ -16465,31 +17049,37 @@ function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
|
16465
17049
|
}
|
|
16466
17050
|
return outputDir;
|
|
16467
17051
|
}
|
|
16468
|
-
function artifactTargets(path, videoDependencies,
|
|
17052
|
+
function artifactTargets(path, videoDependencies, chunkAudio) {
|
|
16469
17053
|
if (isPlanAudioArtifactPath(path)) {
|
|
16470
|
-
|
|
17054
|
+
if (chunkAudio === "none") return { chunks: [], assembler: true };
|
|
17055
|
+
return { chunks: chunkAudio === "whole" ? "all" : [], assembler: false };
|
|
16471
17056
|
}
|
|
17057
|
+
const slice = chunkAudioSliceTarget(path);
|
|
17058
|
+
if (slice !== null) return { chunks: slice === "all" ? "all" : [slice], assembler: false };
|
|
16472
17059
|
if (path === "plan.json" || path === "meta/chunks.json" || path === "meta/encoder.json") {
|
|
16473
17060
|
return { chunks: "all", assembler: true };
|
|
16474
17061
|
}
|
|
16475
|
-
if (path
|
|
16476
|
-
return { chunks: "all", assembler: false };
|
|
16477
|
-
}
|
|
16478
|
-
if (path.startsWith("video-frames/") || path.startsWith("video-sources/")) {
|
|
17062
|
+
if (path.startsWith("video-frames/") || path.startsWith("video-sources/") || path.startsWith("compiled/") && videoDependencies?.has(path)) {
|
|
16479
17063
|
return {
|
|
16480
17064
|
chunks: videoDependencies === null ? "all" : videoDependencies.get(path) ?? [],
|
|
16481
17065
|
assembler: false
|
|
16482
17066
|
};
|
|
16483
17067
|
}
|
|
17068
|
+
if (path === "meta/composition.json" || path === "meta/videos.json" || path.startsWith("compiled/")) {
|
|
17069
|
+
return { chunks: "all", assembler: false };
|
|
17070
|
+
}
|
|
16484
17071
|
return { chunks: "all", assembler: true };
|
|
16485
17072
|
}
|
|
17073
|
+
function isPlanV2DeferredSourceArtifact(artifact) {
|
|
17074
|
+
return artifact.path.startsWith("compiled/") && artifact.chunks !== "all";
|
|
17075
|
+
}
|
|
16486
17076
|
function listVideoFramePaths(executionPlanDir, videos) {
|
|
16487
17077
|
return videos.extracted.map((video) => {
|
|
16488
17078
|
const outputDir = resolveExtractedVideoOutputDir(executionPlanDir, video.videoId);
|
|
16489
17079
|
if (video.deferredRange) {
|
|
16490
17080
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
16491
17081
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
16492
|
-
framePaths2.set(index,
|
|
17082
|
+
framePaths2.set(index, join39(outputDir, String(index)));
|
|
16493
17083
|
}
|
|
16494
17084
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
16495
17085
|
}
|
|
@@ -16497,7 +17087,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
16497
17087
|
const framePaths = /* @__PURE__ */ new Map();
|
|
16498
17088
|
for (const frameName of frameNames) {
|
|
16499
17089
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
16500
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
17090
|
+
assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
|
|
16501
17091
|
continue;
|
|
16502
17092
|
}
|
|
16503
17093
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -16516,7 +17106,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
16516
17106
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
16517
17107
|
);
|
|
16518
17108
|
}
|
|
16519
|
-
framePaths.set(frameIndex,
|
|
17109
|
+
framePaths.set(frameIndex, join39(outputDir, frameName));
|
|
16520
17110
|
}
|
|
16521
17111
|
return {
|
|
16522
17112
|
...video,
|
|
@@ -16537,11 +17127,11 @@ function parsePlanVideosJson2(value) {
|
|
|
16537
17127
|
}
|
|
16538
17128
|
}
|
|
16539
17129
|
function materializeExtractedVideoDirectories(planDir) {
|
|
16540
|
-
const videosPath =
|
|
16541
|
-
if (!
|
|
17130
|
+
const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
17131
|
+
if (!existsSync25(videosPath)) return;
|
|
16542
17132
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
16543
17133
|
for (const video of videos.extracted) {
|
|
16544
|
-
|
|
17134
|
+
mkdirSync15(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
16545
17135
|
}
|
|
16546
17136
|
}
|
|
16547
17137
|
function parseChunkSlices(value) {
|
|
@@ -16566,13 +17156,13 @@ function parseChunkSlices(value) {
|
|
|
16566
17156
|
});
|
|
16567
17157
|
}
|
|
16568
17158
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
16569
|
-
const videoRoot =
|
|
16570
|
-
const hasExtractedFrames =
|
|
16571
|
-
const videosPath =
|
|
16572
|
-
if (!
|
|
17159
|
+
const videoRoot = join39(executionPlanDir, "video-frames");
|
|
17160
|
+
const hasExtractedFrames = existsSync25(videoRoot) && listFiles(videoRoot).length > 0;
|
|
17161
|
+
const videosPath = join39(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
17162
|
+
if (!existsSync25(videosPath)) {
|
|
16573
17163
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
16574
17164
|
}
|
|
16575
|
-
const chunksPath =
|
|
17165
|
+
const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
|
|
16576
17166
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
16577
17167
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
16578
17168
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -16626,15 +17216,15 @@ function computeManifestHash(manifest) {
|
|
|
16626
17216
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
16627
17217
|
}
|
|
16628
17218
|
function writeBlob(sourcePath, destinationPath) {
|
|
16629
|
-
if (
|
|
16630
|
-
|
|
16631
|
-
const temporaryDir =
|
|
16632
|
-
const temporaryPath =
|
|
17219
|
+
if (existsSync25(destinationPath)) return;
|
|
17220
|
+
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
17221
|
+
const temporaryDir = mkdtempSync7(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
17222
|
+
const temporaryPath = join39(temporaryDir, "blob");
|
|
16633
17223
|
try {
|
|
16634
17224
|
copyFileSync6(sourcePath, temporaryPath);
|
|
16635
|
-
|
|
17225
|
+
renameSync7(temporaryPath, destinationPath);
|
|
16636
17226
|
} finally {
|
|
16637
|
-
|
|
17227
|
+
rmSync13(temporaryDir, { recursive: true, force: true });
|
|
16638
17228
|
}
|
|
16639
17229
|
}
|
|
16640
17230
|
function publishBlobFor(absolutePath, digests) {
|
|
@@ -16645,14 +17235,17 @@ function publishBlobFor(absolutePath, digests) {
|
|
|
16645
17235
|
};
|
|
16646
17236
|
}
|
|
16647
17237
|
async function putBlobs(publisher, blobs) {
|
|
17238
|
+
await putTasks(blobs.map((blob) => () => publisher.putBlob(blob)));
|
|
17239
|
+
}
|
|
17240
|
+
async function putTasks(tasks) {
|
|
16648
17241
|
const uploadSlots = 32;
|
|
16649
17242
|
let next = 0;
|
|
16650
17243
|
let failure;
|
|
16651
17244
|
const upload = async () => {
|
|
16652
|
-
while (!failure && next <
|
|
16653
|
-
const
|
|
17245
|
+
while (!failure && next < tasks.length) {
|
|
17246
|
+
const task = tasks[next++];
|
|
16654
17247
|
try {
|
|
16655
|
-
await
|
|
17248
|
+
await task();
|
|
16656
17249
|
} catch (reason) {
|
|
16657
17250
|
failure ??= { reason };
|
|
16658
17251
|
}
|
|
@@ -16662,8 +17255,8 @@ async function putBlobs(publisher, blobs) {
|
|
|
16662
17255
|
if (failure) throw failure.reason;
|
|
16663
17256
|
}
|
|
16664
17257
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
16665
|
-
const executionPlanPath =
|
|
16666
|
-
if (!
|
|
17258
|
+
const executionPlanPath = join39(executionPlanDir, "plan.json");
|
|
17259
|
+
if (!existsSync25(executionPlanPath)) {
|
|
16667
17260
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
16668
17261
|
}
|
|
16669
17262
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -16690,11 +17283,12 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
16690
17283
|
}
|
|
16691
17284
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
16692
17285
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
16693
|
-
const encoder = readJsonFile(
|
|
16694
|
-
const
|
|
16695
|
-
|
|
17286
|
+
const encoder = readJsonFile(join39(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
17287
|
+
const files = listFiles(executionPlanDir);
|
|
17288
|
+
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
17289
|
+
for (const file of files) {
|
|
16696
17290
|
if (isExtractionCacheCompleteSentinelPath(file.path)) continue;
|
|
16697
|
-
const targets = artifactTargets(file.path, videoDependencyPlan.dependencies,
|
|
17291
|
+
const targets = artifactTargets(file.path, videoDependencyPlan.dependencies, chunkAudio);
|
|
16698
17292
|
if ((file.path.startsWith("video-frames/") || file.path.startsWith("video-sources/")) && targets.chunks !== "all" && targets.chunks.length === 0 && !targets.assembler) {
|
|
16699
17293
|
continue;
|
|
16700
17294
|
}
|
|
@@ -16707,6 +17301,15 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
16707
17301
|
...targets
|
|
16708
17302
|
});
|
|
16709
17303
|
}
|
|
17304
|
+
const storedBlobs = /* @__PURE__ */ new Map();
|
|
17305
|
+
for (const remote of readPlanRemoteFiles(executionPlanDir)) {
|
|
17306
|
+
const { sha256, sizeBytes } = remote;
|
|
17307
|
+
if (!blobs.has(sha256)) storedBlobs.set(sha256, { sha256, sizeBytes });
|
|
17308
|
+
artifacts.push({
|
|
17309
|
+
...remote,
|
|
17310
|
+
...artifactTargets(remote.path, videoDependencyPlan.dependencies, chunkAudio)
|
|
17311
|
+
});
|
|
17312
|
+
}
|
|
16710
17313
|
const base = {
|
|
16711
17314
|
protocol: PLAN_PROTOCOL_V2,
|
|
16712
17315
|
// Retain the established manifest key byte-for-byte. It now acts as the
|
|
@@ -16728,708 +17331,397 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
16728
17331
|
...base,
|
|
16729
17332
|
planHash: computeManifestHash(base)
|
|
16730
17333
|
},
|
|
16731
|
-
blobs: [...blobs.values()]
|
|
16732
|
-
|
|
16733
|
-
}
|
|
16734
|
-
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
16735
|
-
if (existsSync24(planV2Dir)) {
|
|
16736
|
-
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
16737
|
-
}
|
|
16738
|
-
const publication = buildPlanV2Publication(executionPlanDir);
|
|
16739
|
-
mkdirSync13(dirname14(planV2Dir), { recursive: true });
|
|
16740
|
-
const tempDir = mkdtempSync6(join37(dirname14(planV2Dir), ".plan-v2-build-"));
|
|
16741
|
-
try {
|
|
16742
|
-
for (const blob of publication.blobs) {
|
|
16743
|
-
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
16744
|
-
}
|
|
16745
|
-
writeFileSync10(
|
|
16746
|
-
join37(tempDir, "plan.json"),
|
|
16747
|
-
canonicalJsonStringify(publication.manifest),
|
|
16748
|
-
"utf-8"
|
|
16749
|
-
);
|
|
16750
|
-
renameSync6(tempDir, planV2Dir);
|
|
16751
|
-
return resultFromManifest(planV2Dir, publication.manifest);
|
|
16752
|
-
} catch (error) {
|
|
16753
|
-
rmSync12(tempDir, { recursive: true, force: true });
|
|
16754
|
-
throw error;
|
|
16755
|
-
}
|
|
16756
|
-
}
|
|
16757
|
-
function createPlanV2FromV1(executionPlanDir, planV2Dir) {
|
|
16758
|
-
return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
|
|
16759
|
-
}
|
|
16760
|
-
async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
|
|
16761
|
-
return publishExecutionPlan(executionPlanDir, publisher, {});
|
|
16762
|
-
}
|
|
16763
|
-
async function publishExecutionPlan(executionPlanDir, publisher, { digests, log = defaultLogger }) {
|
|
16764
|
-
try {
|
|
16765
|
-
let stageStarted = Date.now();
|
|
16766
|
-
const publication = buildPlanV2Publication(executionPlanDir, digests);
|
|
16767
|
-
logPlanStage(log, "publication", stageStarted);
|
|
16768
|
-
stageStarted = Date.now();
|
|
16769
|
-
await putBlobs(publisher, publication.blobs);
|
|
16770
|
-
logPlanStage(log, "upload", stageStarted);
|
|
16771
|
-
stageStarted = Date.now();
|
|
16772
|
-
await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
|
|
16773
|
-
logPlanStage(log, "manifest", stageStarted);
|
|
16774
|
-
return publication.manifest;
|
|
16775
|
-
} catch (error) {
|
|
16776
|
-
try {
|
|
16777
|
-
await publisher.abort();
|
|
16778
|
-
} catch {
|
|
16779
|
-
}
|
|
16780
|
-
throw error;
|
|
16781
|
-
}
|
|
16782
|
-
}
|
|
16783
|
-
async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
16784
|
-
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
16785
|
-
}
|
|
16786
|
-
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
16787
|
-
const stagingRoot = mkdtempSync6(join37(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
16788
|
-
const digests = /* @__PURE__ */ new Map();
|
|
16789
|
-
try {
|
|
16790
|
-
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
16791
|
-
executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
|
|
16792
|
-
deferVideoExtraction: true,
|
|
16793
|
-
digests,
|
|
16794
|
-
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
16795
|
-
whileAudioMixes: () => putBlobs(
|
|
16796
|
-
publisher,
|
|
16797
|
-
listFiles(join37(stagingRoot, "compiled")).map(
|
|
16798
|
-
(file) => publishBlobFor(file.absolutePath, digests)
|
|
16799
|
-
)
|
|
16800
|
-
),
|
|
16801
|
-
chunksCarryAudio: options.chunksCarryAudio
|
|
16802
|
-
});
|
|
16803
|
-
return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
|
|
16804
|
-
} catch (error) {
|
|
16805
|
-
try {
|
|
16806
|
-
await publisher.abort();
|
|
16807
|
-
} catch {
|
|
16808
|
-
}
|
|
16809
|
-
throw error;
|
|
16810
|
-
} finally {
|
|
16811
|
-
rmSync12(stagingRoot, { recursive: true, force: true });
|
|
16812
|
-
}
|
|
16813
|
-
}
|
|
16814
|
-
async function planV2(projectDir, config, planV2Dir) {
|
|
16815
|
-
if (existsSync24(planV2Dir)) {
|
|
16816
|
-
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
16817
|
-
}
|
|
16818
|
-
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
16819
|
-
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
16820
|
-
stagingParentDir: dirname14(planV2Dir)
|
|
16821
|
-
});
|
|
16822
|
-
return resultFromManifest(planV2Dir, manifest);
|
|
16823
|
-
}
|
|
16824
|
-
function resultFromManifest(planV2Dir, manifest) {
|
|
16825
|
-
return {
|
|
16826
|
-
planDir: planV2Dir,
|
|
16827
|
-
manifestPath: join37(planV2Dir, "plan.json"),
|
|
16828
|
-
planProtocol: PLAN_PROTOCOL_V2,
|
|
16829
|
-
planHash: manifest.planHash,
|
|
16830
|
-
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
16831
|
-
chunkCount: manifest.chunkCount,
|
|
16832
|
-
totalFrames: manifest.totalFrames,
|
|
16833
|
-
fps: manifest.fps,
|
|
16834
|
-
width: manifest.width,
|
|
16835
|
-
height: manifest.height,
|
|
16836
|
-
format: manifest.format,
|
|
16837
|
-
ffmpegVersion: manifest.ffmpegVersion,
|
|
16838
|
-
producerVersion: manifest.producerVersion,
|
|
16839
|
-
limitations: manifest.limitations
|
|
16840
|
-
};
|
|
16841
|
-
}
|
|
16842
|
-
function getPlanV2ExecutionPlanHash(plan2) {
|
|
16843
|
-
return plan2.sourcePlanV1Hash;
|
|
16844
|
-
}
|
|
16845
|
-
function readString2(value, field) {
|
|
16846
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
16847
|
-
throw new PlanV2IntegrityError(`${field} must be a non-empty string`);
|
|
16848
|
-
}
|
|
16849
|
-
return value;
|
|
16850
|
-
}
|
|
16851
|
-
function readPositiveInteger2(value, field) {
|
|
16852
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
16853
|
-
throw new PlanV2IntegrityError(`${field} must be a positive integer`);
|
|
16854
|
-
}
|
|
16855
|
-
return value;
|
|
16856
|
-
}
|
|
16857
|
-
function readNonNegativeInteger2(value, field) {
|
|
16858
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
16859
|
-
throw new PlanV2IntegrityError(`${field} must be a non-negative integer`);
|
|
16860
|
-
}
|
|
16861
|
-
return value;
|
|
16862
|
-
}
|
|
16863
|
-
function readSupportedFps(value) {
|
|
16864
|
-
if (!isDistributedFps(value)) {
|
|
16865
|
-
throw new PlanV2IntegrityError(
|
|
16866
|
-
`dimensions.fpsNum must be one of ${DISTRIBUTED_FPS.join(", ")}`
|
|
16867
|
-
);
|
|
16868
|
-
}
|
|
16869
|
-
return value;
|
|
16870
|
-
}
|
|
16871
|
-
function readExecutionPlanFps(dimensions) {
|
|
16872
|
-
const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
|
|
16873
|
-
if (fpsDen !== 1) {
|
|
16874
|
-
throw new PlanV2IntegrityError("dimensions.fpsDen must be 1 for plan v2");
|
|
16875
|
-
}
|
|
16876
|
-
return readSupportedFps(dimensions.fpsNum);
|
|
16877
|
-
}
|
|
16878
|
-
function readDistributedFormat(value) {
|
|
16879
|
-
if (value !== "mp4" && value !== "mov" && value !== "webm" && value !== "png-sequence") {
|
|
16880
|
-
throw new PlanV2IntegrityError("dimensions.format is unsupported");
|
|
16881
|
-
}
|
|
16882
|
-
return value;
|
|
16883
|
-
}
|
|
16884
|
-
function parseArtifact(value, index) {
|
|
16885
|
-
if (!isRecord3(value)) throw new PlanV2IntegrityError(`artifacts[${index}] must be an object`);
|
|
16886
|
-
const path = readString2(value.path, `artifacts[${index}].path`);
|
|
16887
|
-
assertSafeRelativePath(path);
|
|
16888
|
-
if (!isSha256(value.sha256)) {
|
|
16889
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].sha256 must be a lowercase sha256`);
|
|
16890
|
-
}
|
|
16891
|
-
const sizeBytes = value.sizeBytes;
|
|
16892
|
-
if (typeof sizeBytes !== "number" || !Number.isInteger(sizeBytes) || sizeBytes < 0) {
|
|
16893
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].sizeBytes must be a non-negative integer`);
|
|
16894
|
-
}
|
|
16895
|
-
let chunks;
|
|
16896
|
-
if (value.chunks === "all") {
|
|
16897
|
-
chunks = "all";
|
|
16898
|
-
} else if (isNonNegativeIntegerArray(value.chunks)) {
|
|
16899
|
-
chunks = value.chunks;
|
|
16900
|
-
} else {
|
|
16901
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].chunks is invalid`);
|
|
16902
|
-
}
|
|
16903
|
-
if (typeof value.assembler !== "boolean") {
|
|
16904
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].assembler must be boolean`);
|
|
16905
|
-
}
|
|
16906
|
-
return {
|
|
16907
|
-
path,
|
|
16908
|
-
sha256: value.sha256,
|
|
16909
|
-
sizeBytes,
|
|
16910
|
-
chunks,
|
|
16911
|
-
assembler: value.assembler
|
|
16912
|
-
};
|
|
16913
|
-
}
|
|
16914
|
-
function parsePlanV2Manifest(value) {
|
|
16915
|
-
if (!isRecord3(value)) throw new PlanV2IntegrityError("manifest must be an object");
|
|
16916
|
-
if (!isRecord3(value.protocol) || value.protocol.schemaVersion !== PLAN_PROTOCOL_V2.schemaVersion || value.protocol.artifactLayout !== PLAN_PROTOCOL_V2.artifactLayout || value.protocol.hashSchema !== PLAN_PROTOCOL_V2.hashSchema) {
|
|
16917
|
-
throw new PlanV2IntegrityError("manifest protocol is not the supported v2 descriptor");
|
|
16918
|
-
}
|
|
16919
|
-
if (!isSha256(value.planHash) || !isSha256(value.sourcePlanV1Hash)) {
|
|
16920
|
-
throw new PlanV2IntegrityError("manifest hashes must be lowercase sha256 digests");
|
|
16921
|
-
}
|
|
16922
|
-
if (!Array.isArray(value.artifacts)) throw new PlanV2IntegrityError("artifacts must be an array");
|
|
16923
|
-
const artifacts = value.artifacts.map(parseArtifact);
|
|
16924
|
-
const paths = /* @__PURE__ */ new Set();
|
|
16925
|
-
for (const artifact of artifacts) {
|
|
16926
|
-
if (paths.has(artifact.path))
|
|
16927
|
-
throw new PlanV2IntegrityError(`duplicate artifact path: ${artifact.path}`);
|
|
16928
|
-
paths.add(artifact.path);
|
|
16929
|
-
}
|
|
16930
|
-
if (!paths.has("plan.json"))
|
|
16931
|
-
throw new PlanV2IntegrityError("manifest must include the local execution plan.json artifact");
|
|
16932
|
-
if (!isRecord3(value.limitations) || value.limitations.videoDependencyMode !== "exact-rendered-frames" && value.limitations.videoDependencyMode !== "full-source-pack" && value.limitations.videoDependencyMode !== "source-extract") {
|
|
16933
|
-
throw new PlanV2IntegrityError("unsupported video dependency mode");
|
|
16934
|
-
}
|
|
16935
|
-
const parsed = {
|
|
16936
|
-
protocol: PLAN_PROTOCOL_V2,
|
|
16937
|
-
planHash: value.planHash,
|
|
16938
|
-
sourcePlanV1Hash: value.sourcePlanV1Hash,
|
|
16939
|
-
chunkCount: readPositiveInteger2(value.chunkCount, "chunkCount"),
|
|
16940
|
-
totalFrames: readPositiveInteger2(value.totalFrames, "totalFrames"),
|
|
16941
|
-
fps: readSupportedFps(value.fps),
|
|
16942
|
-
width: readPositiveInteger2(value.width, "width"),
|
|
16943
|
-
height: readPositiveInteger2(value.height, "height"),
|
|
16944
|
-
format: readDistributedFormat(value.format),
|
|
16945
|
-
ffmpegVersion: readString2(value.ffmpegVersion, "ffmpegVersion"),
|
|
16946
|
-
producerVersion: readString2(value.producerVersion, "producerVersion"),
|
|
16947
|
-
limitations: { videoDependencyMode: value.limitations.videoDependencyMode },
|
|
16948
|
-
artifacts
|
|
16949
|
-
};
|
|
16950
|
-
const { planHash: _planHash, ...payload } = parsed;
|
|
16951
|
-
const expectedHash = computeManifestHash(payload);
|
|
16952
|
-
if (expectedHash !== parsed.planHash) {
|
|
16953
|
-
throw new PlanV2IntegrityError("manifest integrity hash mismatch");
|
|
16954
|
-
}
|
|
16955
|
-
for (const [index, artifact] of parsed.artifacts.entries()) {
|
|
16956
|
-
if (artifact.chunks !== "all" && artifact.chunks.some((chunkIndex) => chunkIndex >= parsed.chunkCount)) {
|
|
16957
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].chunks contains an out-of-range index`);
|
|
16958
|
-
}
|
|
16959
|
-
}
|
|
16960
|
-
return parsed;
|
|
16961
|
-
}
|
|
16962
|
-
function readPlanV2Manifest(planV2Dir) {
|
|
16963
|
-
const manifestPath = join37(planV2Dir, "plan.json");
|
|
16964
|
-
if (!existsSync24(manifestPath)) {
|
|
16965
|
-
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
16966
|
-
}
|
|
16967
|
-
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
16968
|
-
return parsePlanV2Manifest(value);
|
|
16969
|
-
}
|
|
16970
|
-
function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
16971
|
-
if (target.role === "chunk") {
|
|
16972
|
-
if (!Number.isInteger(target.chunkIndex) || target.chunkIndex < 0 || target.chunkIndex >= manifest.chunkCount) {
|
|
16973
|
-
throw new PlanV2IntegrityError(`chunkIndex ${String(target.chunkIndex)} is out of range`);
|
|
16974
|
-
}
|
|
16975
|
-
return manifest.artifacts.filter(
|
|
16976
|
-
(artifact) => artifact.chunks === "all" || artifact.chunks.some((chunkIndex) => chunkIndex === target.chunkIndex)
|
|
16977
|
-
);
|
|
16978
|
-
}
|
|
16979
|
-
return manifest.artifacts.filter((artifact) => artifact.assembler);
|
|
17334
|
+
blobs: [...blobs.values()],
|
|
17335
|
+
storedBlobs: [...storedBlobs.values()]
|
|
17336
|
+
};
|
|
16980
17337
|
}
|
|
16981
|
-
function
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
16985
|
-
}
|
|
16986
|
-
const stats = statSync9(sourcePath);
|
|
16987
|
-
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
16988
|
-
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
16989
|
-
}
|
|
16990
|
-
if (sha256File(sourcePath) !== artifact.sha256) {
|
|
16991
|
-
throw new PlanV2IntegrityError(`artifact hash mismatch for ${artifact.path}`);
|
|
17338
|
+
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
17339
|
+
if (existsSync25(planV2Dir)) {
|
|
17340
|
+
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
16992
17341
|
}
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
if (existsSync24(destinationDir)) {
|
|
16997
|
-
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
17342
|
+
const publication = buildPlanV2Publication(executionPlanDir);
|
|
17343
|
+
if (publication.storedBlobs.length > 0) {
|
|
17344
|
+
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
16998
17345
|
}
|
|
16999
|
-
|
|
17000
|
-
const
|
|
17001
|
-
const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
|
|
17002
|
-
const verified = artifacts.map((artifact) => ({
|
|
17003
|
-
artifact,
|
|
17004
|
-
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
17005
|
-
}));
|
|
17006
|
-
mkdirSync13(dirname14(destinationDir), { recursive: true });
|
|
17007
|
-
const tempDir = mkdtempSync6(join37(dirname14(destinationDir), ".plan-v2-materialize-"));
|
|
17346
|
+
mkdirSync15(dirname15(planV2Dir), { recursive: true });
|
|
17347
|
+
const tempDir = mkdtempSync7(join39(dirname15(planV2Dir), ".plan-v2-build-"));
|
|
17008
17348
|
try {
|
|
17009
|
-
for (const
|
|
17010
|
-
|
|
17011
|
-
mkdirSync13(dirname14(destinationPath), { recursive: true });
|
|
17012
|
-
copyFileSync6(sourcePath, destinationPath);
|
|
17349
|
+
for (const blob of publication.blobs) {
|
|
17350
|
+
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
17013
17351
|
}
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
canonicalJsonStringify({ manifest, target }),
|
|
17352
|
+
writeFileSync11(
|
|
17353
|
+
join39(tempDir, "plan.json"),
|
|
17354
|
+
canonicalJsonStringify(publication.manifest),
|
|
17018
17355
|
"utf-8"
|
|
17019
17356
|
);
|
|
17020
|
-
|
|
17357
|
+
renameSync7(tempDir, planV2Dir);
|
|
17358
|
+
return resultFromManifest(planV2Dir, publication.manifest);
|
|
17021
17359
|
} catch (error) {
|
|
17022
|
-
|
|
17360
|
+
rmSync13(tempDir, { recursive: true, force: true });
|
|
17023
17361
|
throw error;
|
|
17024
17362
|
}
|
|
17025
|
-
return {
|
|
17026
|
-
planDir: destinationDir,
|
|
17027
|
-
target,
|
|
17028
|
-
planHash: manifest.planHash,
|
|
17029
|
-
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
17030
|
-
artifactCount: artifacts.length,
|
|
17031
|
-
sizeBytes: artifacts.reduce((sum2, artifact) => sum2 + artifact.sizeBytes, 0),
|
|
17032
|
-
// Join the artifact's OWN name, not the current constant: a plan written
|
|
17033
|
-
// before the container change carries the legacy name, and materializing it
|
|
17034
|
-
// under the new one would point at a file that was never written.
|
|
17035
|
-
audioPath: target.role === "assembler" ? audioArtifact ? join37(destinationDir, audioArtifact.path) : null : null
|
|
17036
|
-
};
|
|
17037
|
-
}
|
|
17038
|
-
function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
17039
|
-
const markerPath = join37(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
17040
|
-
if (!existsSync24(markerPath)) return null;
|
|
17041
|
-
const marker = readJsonFile(markerPath, "materialization marker");
|
|
17042
|
-
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
17043
|
-
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
17044
|
-
}
|
|
17045
|
-
const target = marker.target;
|
|
17046
|
-
if (target.role !== expectedTarget.role || expectedTarget.role === "chunk" && target.chunkIndex !== expectedTarget.chunkIndex) {
|
|
17047
|
-
throw new PlanV2IntegrityError(
|
|
17048
|
-
"materialization target does not match requested execution role"
|
|
17049
|
-
);
|
|
17050
|
-
}
|
|
17051
|
-
const manifest = parsePlanV2Manifest(marker.manifest);
|
|
17052
|
-
const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
|
|
17053
|
-
for (const artifact of required) {
|
|
17054
|
-
const path = join37(planDir, ...artifact.path.split("/"));
|
|
17055
|
-
if (!existsSync24(path) || statSync9(path).size !== artifact.sizeBytes) {
|
|
17056
|
-
throw new PlanV2IntegrityError(
|
|
17057
|
-
`materialized artifact missing or truncated: ${artifact.path}`
|
|
17058
|
-
);
|
|
17059
|
-
}
|
|
17060
|
-
if (sha256File(path) !== artifact.sha256) {
|
|
17061
|
-
throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
|
|
17062
|
-
}
|
|
17063
|
-
}
|
|
17064
|
-
return manifest;
|
|
17065
|
-
}
|
|
17066
|
-
|
|
17067
|
-
// ../producer/src/services/distributed/planV2Execution.ts
|
|
17068
|
-
import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
|
|
17069
|
-
import { tmpdir as tmpdir5 } from "os";
|
|
17070
|
-
import { join as join41 } from "path";
|
|
17071
|
-
|
|
17072
|
-
// ../producer/src/services/distributed/assemble.ts
|
|
17073
|
-
import {
|
|
17074
|
-
cpSync as cpSync3,
|
|
17075
|
-
existsSync as existsSync25,
|
|
17076
|
-
mkdirSync as mkdirSync15,
|
|
17077
|
-
mkdtempSync as mkdtempSync8,
|
|
17078
|
-
readFileSync as readFileSync12,
|
|
17079
|
-
readdirSync as readdirSync10,
|
|
17080
|
-
renameSync as renameSync8,
|
|
17081
|
-
rmSync as rmSync14,
|
|
17082
|
-
statSync as statSync10,
|
|
17083
|
-
writeFileSync as writeFileSync11
|
|
17084
|
-
} from "fs";
|
|
17085
|
-
import { dirname as dirname16, join as join39 } from "path";
|
|
17086
|
-
|
|
17087
|
-
// ../producer/src/services/distributed/mp4Stitch.ts
|
|
17088
|
-
import {
|
|
17089
|
-
createReadStream as createReadStream5,
|
|
17090
|
-
createWriteStream,
|
|
17091
|
-
mkdirSync as mkdirSync14,
|
|
17092
|
-
mkdtempSync as mkdtempSync7,
|
|
17093
|
-
renameSync as renameSync7,
|
|
17094
|
-
rmSync as rmSync13
|
|
17095
|
-
} from "fs";
|
|
17096
|
-
import { open } from "fs/promises";
|
|
17097
|
-
import { dirname as dirname15, join as join38 } from "path";
|
|
17098
|
-
import { finished, pipeline } from "stream/promises";
|
|
17099
|
-
function boxAt(src, off, at, limit) {
|
|
17100
|
-
let size = src.readUInt32BE(off);
|
|
17101
|
-
let bodyStart = at + 8;
|
|
17102
|
-
if (size === 1) {
|
|
17103
|
-
size = Number(src.readBigUInt64BE(off + 8));
|
|
17104
|
-
bodyStart = at + 16;
|
|
17105
|
-
}
|
|
17106
|
-
if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
|
|
17107
|
-
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
17108
17363
|
}
|
|
17109
|
-
function
|
|
17110
|
-
|
|
17111
|
-
for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
|
|
17112
|
-
boxes.push(boxAt(buf, at, at, parent.end));
|
|
17113
|
-
}
|
|
17114
|
-
return boxes;
|
|
17115
|
-
}
|
|
17116
|
-
function findChild(buf, parent, type) {
|
|
17117
|
-
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
17364
|
+
function createPlanV2FromV1(executionPlanDir, planV2Dir) {
|
|
17365
|
+
return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
|
|
17118
17366
|
}
|
|
17119
|
-
function
|
|
17120
|
-
|
|
17121
|
-
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
17122
|
-
return found;
|
|
17367
|
+
async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
|
|
17368
|
+
return publishExecutionPlan(executionPlanDir, publisher, {});
|
|
17123
17369
|
}
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
if (start >= bufAt && end <= bufAt + buf.length) return;
|
|
17132
|
-
buf = await read(start, Math.max(end, start + SCAN_BYTES));
|
|
17133
|
-
bufAt = start;
|
|
17134
|
-
};
|
|
17135
|
-
for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
|
|
17136
|
-
await load(at, at + 16);
|
|
17137
|
-
if (at + 8 > bufAt + buf.length) {
|
|
17138
|
-
throw new Error(`[mp4Stitch] chunk needs ftyp, moov and mdat; found ${[...found.keys()]}`);
|
|
17370
|
+
async function publishExecutionPlan(executionPlanDir, publisher, { digests, log = defaultLogger }) {
|
|
17371
|
+
try {
|
|
17372
|
+
let stageStarted = Date.now();
|
|
17373
|
+
const publication = buildPlanV2Publication(executionPlanDir, digests);
|
|
17374
|
+
logPlanStage(log, "publication", stageStarted);
|
|
17375
|
+
if (publication.storedBlobs.length > 0 && !publisher.referenceStoredBlob) {
|
|
17376
|
+
throw new PlanV2IntegrityError("this publisher cannot reference remote files");
|
|
17139
17377
|
}
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17378
|
+
stageStarted = Date.now();
|
|
17379
|
+
await putTasks([
|
|
17380
|
+
...publication.blobs.map((blob) => () => publisher.putBlob(blob)),
|
|
17381
|
+
...publication.storedBlobs.map((blob) => () => publisher.referenceStoredBlob(blob))
|
|
17382
|
+
]);
|
|
17383
|
+
logPlanStage(log, "upload", stageStarted);
|
|
17384
|
+
stageStarted = Date.now();
|
|
17385
|
+
await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
|
|
17386
|
+
logPlanStage(log, "manifest", stageStarted);
|
|
17387
|
+
return publication.manifest;
|
|
17388
|
+
} catch (error) {
|
|
17389
|
+
try {
|
|
17390
|
+
await publisher.abort();
|
|
17391
|
+
} catch {
|
|
17145
17392
|
}
|
|
17146
|
-
|
|
17393
|
+
throw error;
|
|
17147
17394
|
}
|
|
17148
|
-
const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
|
|
17149
|
-
if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
|
|
17150
|
-
return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
|
|
17151
|
-
}
|
|
17152
|
-
var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
|
|
17153
|
-
function readTracks(moov) {
|
|
17154
|
-
const root = boxAt(moov, 0, 0, moov.length);
|
|
17155
|
-
return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
|
|
17156
17395
|
}
|
|
17157
|
-
function
|
|
17158
|
-
|
|
17159
|
-
const first = table.bodyStart + countAt + 4;
|
|
17160
|
-
return Array.from(
|
|
17161
|
-
{ length: count },
|
|
17162
|
-
(_, row) => Array.from(
|
|
17163
|
-
{ length: columns },
|
|
17164
|
-
(_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
|
|
17165
|
-
)
|
|
17166
|
-
);
|
|
17396
|
+
async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
17397
|
+
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
17167
17398
|
}
|
|
17168
|
-
function
|
|
17169
|
-
const
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17399
|
+
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
17400
|
+
const stagingRoot = mkdtempSync7(join39(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
17401
|
+
const digests = /* @__PURE__ */ new Map();
|
|
17402
|
+
try {
|
|
17403
|
+
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
17404
|
+
executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
|
|
17405
|
+
deferVideoExtraction: true,
|
|
17406
|
+
remoteFiles: options.remoteFiles,
|
|
17407
|
+
digests,
|
|
17408
|
+
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
17409
|
+
whileAudioMixes: () => putBlobs(
|
|
17410
|
+
publisher,
|
|
17411
|
+
listFiles(join39(stagingRoot, "compiled")).map(
|
|
17412
|
+
(file) => publishBlobFor(file.absolutePath, digests)
|
|
17413
|
+
)
|
|
17414
|
+
),
|
|
17415
|
+
chunksCarryAudio: options.chunksCarryAudio,
|
|
17416
|
+
sliceChunkAudio: true
|
|
17417
|
+
});
|
|
17418
|
+
return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
|
|
17419
|
+
} catch (error) {
|
|
17420
|
+
try {
|
|
17421
|
+
await publisher.abort();
|
|
17422
|
+
} catch {
|
|
17423
|
+
}
|
|
17424
|
+
throw error;
|
|
17425
|
+
} finally {
|
|
17426
|
+
rmSync13(stagingRoot, { recursive: true, force: true });
|
|
17178
17427
|
}
|
|
17179
|
-
return 0;
|
|
17180
|
-
}
|
|
17181
|
-
function readSizes(moov, stsz) {
|
|
17182
|
-
const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
|
|
17183
|
-
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
17184
|
-
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
17185
|
-
}
|
|
17186
|
-
function readSync4(moov, stbl, sampleCount) {
|
|
17187
|
-
const stss = findChild(moov, stbl, "stss");
|
|
17188
|
-
if (!stss) return null;
|
|
17189
|
-
const sync = Array(sampleCount).fill(false);
|
|
17190
|
-
for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
|
|
17191
|
-
return sync;
|
|
17192
17428
|
}
|
|
17193
|
-
function
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
17201
|
-
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
17202
|
-
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
17203
|
-
return Array(nextFirst - firstChunk).fill(perChunk);
|
|
17429
|
+
async function planV2(projectDir, config, planV2Dir) {
|
|
17430
|
+
if (existsSync25(planV2Dir)) {
|
|
17431
|
+
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
17432
|
+
}
|
|
17433
|
+
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
17434
|
+
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
17435
|
+
stagingParentDir: dirname15(planV2Dir)
|
|
17204
17436
|
});
|
|
17437
|
+
return resultFromManifest(planV2Dir, manifest);
|
|
17205
17438
|
}
|
|
17206
|
-
function
|
|
17207
|
-
const mdia = child(moov, trak, "mdia");
|
|
17208
|
-
const hdlr = child(moov, mdia, "hdlr");
|
|
17209
|
-
const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
|
|
17210
|
-
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
17211
|
-
const stts = rows(moov, child(moov, stbl, "stts"), 2);
|
|
17212
|
-
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
17213
|
-
const sizes = readSizes(moov, child(moov, stbl, "stsz"));
|
|
17214
|
-
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
17215
|
-
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
17216
|
-
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
17217
|
-
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
17218
|
-
}
|
|
17219
|
-
const stsd = child(moov, stbl, "stsd");
|
|
17439
|
+
function resultFromManifest(planV2Dir, manifest) {
|
|
17220
17440
|
return {
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17441
|
+
planDir: planV2Dir,
|
|
17442
|
+
manifestPath: join39(planV2Dir, "plan.json"),
|
|
17443
|
+
planProtocol: PLAN_PROTOCOL_V2,
|
|
17444
|
+
planHash: manifest.planHash,
|
|
17445
|
+
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
17446
|
+
chunkCount: manifest.chunkCount,
|
|
17447
|
+
totalFrames: manifest.totalFrames,
|
|
17448
|
+
fps: manifest.fps,
|
|
17449
|
+
width: manifest.width,
|
|
17450
|
+
height: manifest.height,
|
|
17451
|
+
format: manifest.format,
|
|
17452
|
+
ffmpegVersion: manifest.ffmpegVersion,
|
|
17453
|
+
producerVersion: manifest.producerVersion,
|
|
17454
|
+
limitations: manifest.limitations
|
|
17231
17455
|
};
|
|
17232
17456
|
}
|
|
17233
|
-
function
|
|
17234
|
-
return
|
|
17457
|
+
function getPlanV2ExecutionPlanHash(plan2) {
|
|
17458
|
+
return plan2.sourcePlanV1Hash;
|
|
17235
17459
|
}
|
|
17236
|
-
function
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
return
|
|
17460
|
+
function readString2(value, field) {
|
|
17461
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
17462
|
+
throw new PlanV2IntegrityError(`${field} must be a non-empty string`);
|
|
17463
|
+
}
|
|
17464
|
+
return value;
|
|
17241
17465
|
}
|
|
17242
|
-
function
|
|
17243
|
-
|
|
17466
|
+
function readPositiveInteger2(value, field) {
|
|
17467
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
17468
|
+
throw new PlanV2IntegrityError(`${field} must be a positive integer`);
|
|
17469
|
+
}
|
|
17470
|
+
return value;
|
|
17244
17471
|
}
|
|
17245
|
-
function
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17472
|
+
function readNonNegativeInteger2(value, field) {
|
|
17473
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
17474
|
+
throw new PlanV2IntegrityError(`${field} must be a non-negative integer`);
|
|
17475
|
+
}
|
|
17476
|
+
return value;
|
|
17249
17477
|
}
|
|
17250
|
-
function
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
else out.push([1, value]);
|
|
17478
|
+
function readSupportedFps(value) {
|
|
17479
|
+
if (!isDistributedFps(value)) {
|
|
17480
|
+
throw new PlanV2IntegrityError(
|
|
17481
|
+
`dimensions.fpsNum must be one of ${DISTRIBUTED_FPS.join(", ")}`
|
|
17482
|
+
);
|
|
17256
17483
|
}
|
|
17257
|
-
return
|
|
17484
|
+
return value;
|
|
17258
17485
|
}
|
|
17259
|
-
function
|
|
17260
|
-
const
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17486
|
+
function readExecutionPlanFps(dimensions) {
|
|
17487
|
+
const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
|
|
17488
|
+
if (fpsDen !== 1) {
|
|
17489
|
+
throw new PlanV2IntegrityError("dimensions.fpsDen must be 1 for plan v2");
|
|
17490
|
+
}
|
|
17491
|
+
return readSupportedFps(dimensions.fpsNum);
|
|
17264
17492
|
}
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
function withDuration(buf, target, duration) {
|
|
17271
|
-
const copy = Buffer.from(buf.subarray(target.start, target.end));
|
|
17272
|
-
const body = target.bodyStart - target.start;
|
|
17273
|
-
const wide = copy[body] === 1;
|
|
17274
|
-
const at = body + DURATION_AT[target.type][wide ? 1 : 0];
|
|
17275
|
-
if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
|
|
17276
|
-
else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
|
|
17277
|
-
else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
|
|
17278
|
-
return copy;
|
|
17493
|
+
function readDistributedFormat(value) {
|
|
17494
|
+
if (value !== "mp4" && value !== "mov" && value !== "webm" && value !== "png-sequence") {
|
|
17495
|
+
throw new PlanV2IntegrityError("dimensions.format is unsupported");
|
|
17496
|
+
}
|
|
17497
|
+
return value;
|
|
17279
17498
|
}
|
|
17280
|
-
function
|
|
17281
|
-
|
|
17499
|
+
function parseArtifact(value, index) {
|
|
17500
|
+
if (!isRecord3(value)) throw new PlanV2IntegrityError(`artifacts[${index}] must be an object`);
|
|
17501
|
+
const path = readString2(value.path, `artifacts[${index}].path`);
|
|
17502
|
+
assertSafeRelativePath(path);
|
|
17503
|
+
if (!isSha256(value.sha256)) {
|
|
17504
|
+
throw new PlanV2IntegrityError(`artifacts[${index}].sha256 must be a lowercase sha256`);
|
|
17505
|
+
}
|
|
17506
|
+
const sizeBytes = value.sizeBytes;
|
|
17507
|
+
if (typeof sizeBytes !== "number" || !Number.isInteger(sizeBytes) || sizeBytes < 0) {
|
|
17508
|
+
throw new PlanV2IntegrityError(`artifacts[${index}].sizeBytes must be a non-negative integer`);
|
|
17509
|
+
}
|
|
17510
|
+
let chunks;
|
|
17511
|
+
if (value.chunks === "all") {
|
|
17512
|
+
chunks = "all";
|
|
17513
|
+
} else if (isNonNegativeIntegerArray(value.chunks)) {
|
|
17514
|
+
chunks = value.chunks;
|
|
17515
|
+
} else {
|
|
17516
|
+
throw new PlanV2IntegrityError(`artifacts[${index}].chunks is invalid`);
|
|
17517
|
+
}
|
|
17518
|
+
if (typeof value.assembler !== "boolean") {
|
|
17519
|
+
throw new PlanV2IntegrityError(`artifacts[${index}].assembler must be boolean`);
|
|
17520
|
+
}
|
|
17521
|
+
return {
|
|
17522
|
+
path,
|
|
17523
|
+
sha256: value.sha256,
|
|
17524
|
+
sizeBytes,
|
|
17525
|
+
chunks,
|
|
17526
|
+
assembler: value.assembler
|
|
17527
|
+
};
|
|
17282
17528
|
}
|
|
17283
|
-
function
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
for (const [count, perChunk] of runs(track.samplesPerChunk)) {
|
|
17288
|
-
stsc.push([firstChunk, perChunk, 1]);
|
|
17289
|
-
firstChunk += count;
|
|
17529
|
+
function parsePlanV2Manifest(value) {
|
|
17530
|
+
if (!isRecord3(value)) throw new PlanV2IntegrityError("manifest must be an object");
|
|
17531
|
+
if (!isRecord3(value.protocol) || value.protocol.schemaVersion !== PLAN_PROTOCOL_V2.schemaVersion || value.protocol.artifactLayout !== PLAN_PROTOCOL_V2.artifactLayout || value.protocol.hashSchema !== PLAN_PROTOCOL_V2.hashSchema) {
|
|
17532
|
+
throw new PlanV2IntegrityError("manifest protocol is not the supported v2 descriptor");
|
|
17290
17533
|
}
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17534
|
+
if (!isSha256(value.planHash) || !isSha256(value.sourcePlanV1Hash)) {
|
|
17535
|
+
throw new PlanV2IntegrityError("manifest hashes must be lowercase sha256 digests");
|
|
17536
|
+
}
|
|
17537
|
+
if (!Array.isArray(value.artifacts)) throw new PlanV2IntegrityError("artifacts must be an array");
|
|
17538
|
+
const artifacts = value.artifacts.map(parseArtifact);
|
|
17539
|
+
const paths = /* @__PURE__ */ new Set();
|
|
17540
|
+
for (const artifact of artifacts) {
|
|
17541
|
+
if (paths.has(artifact.path))
|
|
17542
|
+
throw new PlanV2IntegrityError(`duplicate artifact path: ${artifact.path}`);
|
|
17543
|
+
paths.add(artifact.path);
|
|
17544
|
+
}
|
|
17545
|
+
if (!paths.has("plan.json"))
|
|
17546
|
+
throw new PlanV2IntegrityError("manifest must include the local execution plan.json artifact");
|
|
17547
|
+
if (!isRecord3(value.limitations) || value.limitations.videoDependencyMode !== "exact-rendered-frames" && value.limitations.videoDependencyMode !== "full-source-pack" && value.limitations.videoDependencyMode !== "source-extract") {
|
|
17548
|
+
throw new PlanV2IntegrityError("unsupported video dependency mode");
|
|
17549
|
+
}
|
|
17550
|
+
const parsed = {
|
|
17551
|
+
protocol: PLAN_PROTOCOL_V2,
|
|
17552
|
+
planHash: value.planHash,
|
|
17553
|
+
sourcePlanV1Hash: value.sourcePlanV1Hash,
|
|
17554
|
+
chunkCount: readPositiveInteger2(value.chunkCount, "chunkCount"),
|
|
17555
|
+
totalFrames: readPositiveInteger2(value.totalFrames, "totalFrames"),
|
|
17556
|
+
fps: readSupportedFps(value.fps),
|
|
17557
|
+
width: readPositiveInteger2(value.width, "width"),
|
|
17558
|
+
height: readPositiveInteger2(value.height, "height"),
|
|
17559
|
+
format: readDistributedFormat(value.format),
|
|
17560
|
+
ffmpegVersion: readString2(value.ffmpegVersion, "ffmpegVersion"),
|
|
17561
|
+
producerVersion: readString2(value.producerVersion, "producerVersion"),
|
|
17562
|
+
limitations: { videoDependencyMode: value.limitations.videoDependencyMode },
|
|
17563
|
+
artifacts
|
|
17564
|
+
};
|
|
17565
|
+
const { planHash: _planHash, ...payload } = parsed;
|
|
17566
|
+
const expectedHash = computeManifestHash(payload);
|
|
17567
|
+
if (expectedHash !== parsed.planHash) {
|
|
17568
|
+
throw new PlanV2IntegrityError("manifest integrity hash mismatch");
|
|
17569
|
+
}
|
|
17570
|
+
for (const [index, artifact] of parsed.artifacts.entries()) {
|
|
17571
|
+
if (artifact.chunks !== "all" && artifact.chunks.some((chunkIndex) => chunkIndex >= parsed.chunkCount)) {
|
|
17572
|
+
throw new PlanV2IntegrityError(`artifacts[${index}].chunks contains an out-of-range index`);
|
|
17573
|
+
}
|
|
17574
|
+
}
|
|
17575
|
+
return parsed;
|
|
17304
17576
|
}
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
for (const track of tracks) {
|
|
17318
|
-
if (track.handler === "vide" && track.mediaTime !== 0) {
|
|
17319
|
-
throw new Error(`[mp4Stitch] chunk ${index} video starts at media time ${track.mediaTime}`);
|
|
17577
|
+
function readPlanV2Manifest(planV2Dir) {
|
|
17578
|
+
const manifestPath = join39(planV2Dir, "plan.json");
|
|
17579
|
+
if (!existsSync25(manifestPath)) {
|
|
17580
|
+
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
17581
|
+
}
|
|
17582
|
+
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
17583
|
+
return parsePlanV2Manifest(value);
|
|
17584
|
+
}
|
|
17585
|
+
function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
17586
|
+
if (target.role === "chunk") {
|
|
17587
|
+
if (!Number.isInteger(target.chunkIndex) || target.chunkIndex < 0 || target.chunkIndex >= manifest.chunkCount) {
|
|
17588
|
+
throw new PlanV2IntegrityError(`chunkIndex ${String(target.chunkIndex)} is out of range`);
|
|
17320
17589
|
}
|
|
17590
|
+
return manifest.artifacts.filter(
|
|
17591
|
+
(artifact) => artifact.chunks === "all" || artifact.chunks.some((chunkIndex) => chunkIndex === target.chunkIndex)
|
|
17592
|
+
);
|
|
17321
17593
|
}
|
|
17322
|
-
return
|
|
17594
|
+
return manifest.artifacts.filter((artifact) => artifact.assembler);
|
|
17323
17595
|
}
|
|
17324
|
-
function
|
|
17325
|
-
const
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17596
|
+
function verifyBlob(planV2Dir, artifact) {
|
|
17597
|
+
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
17598
|
+
if (!existsSync25(sourcePath)) {
|
|
17599
|
+
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
17600
|
+
}
|
|
17601
|
+
const stats = statSync9(sourcePath);
|
|
17602
|
+
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
17603
|
+
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
17604
|
+
}
|
|
17605
|
+
if (sha256File(sourcePath) !== artifact.sha256) {
|
|
17606
|
+
throw new PlanV2IntegrityError(`artifact hash mismatch for ${artifact.path}`);
|
|
17607
|
+
}
|
|
17608
|
+
return sourcePath;
|
|
17609
|
+
}
|
|
17610
|
+
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
17611
|
+
if (existsSync25(destinationDir)) {
|
|
17612
|
+
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
17613
|
+
}
|
|
17614
|
+
const manifest = readPlanV2Manifest(planV2Dir);
|
|
17615
|
+
const artifacts = listPlanV2ArtifactsForTarget(manifest, target);
|
|
17616
|
+
const remoteSources = options.remoteSources ?? {};
|
|
17617
|
+
assertRemoteSources(remoteSources, artifacts);
|
|
17618
|
+
const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
|
|
17619
|
+
const verified = artifacts.filter((artifact) => !Object.hasOwn(remoteSources, artifact.path)).map((artifact) => ({
|
|
17620
|
+
artifact,
|
|
17621
|
+
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
17622
|
+
}));
|
|
17623
|
+
mkdirSync15(dirname15(destinationDir), { recursive: true });
|
|
17624
|
+
const tempDir = mkdtempSync7(join39(dirname15(destinationDir), ".plan-v2-materialize-"));
|
|
17625
|
+
try {
|
|
17626
|
+
for (const { artifact, sourcePath } of verified) {
|
|
17627
|
+
const destinationPath = join39(tempDir, ...artifact.path.split("/"));
|
|
17628
|
+
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
17629
|
+
copyFileSync6(sourcePath, destinationPath);
|
|
17630
|
+
}
|
|
17631
|
+
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
17632
|
+
writeFileSync11(
|
|
17633
|
+
join39(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
17634
|
+
canonicalJsonStringify({
|
|
17635
|
+
manifest,
|
|
17636
|
+
target,
|
|
17637
|
+
...Object.keys(remoteSources).length > 0 ? { remoteSources } : {}
|
|
17638
|
+
}),
|
|
17639
|
+
"utf-8"
|
|
17640
|
+
);
|
|
17641
|
+
renameSync7(tempDir, destinationDir);
|
|
17642
|
+
} catch (error) {
|
|
17643
|
+
rmSync13(tempDir, { recursive: true, force: true });
|
|
17644
|
+
throw error;
|
|
17332
17645
|
}
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
sizes: parts.flatMap((part) => part.sizes),
|
|
17345
|
-
sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
|
|
17346
|
-
samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
|
|
17347
|
-
chunkOffsets: parts.flatMap(
|
|
17348
|
-
(part) => part.chunkOffsets.map((offset) => {
|
|
17349
|
-
const i = part.chunk;
|
|
17350
|
-
const { payloadStart, payloadEnd } = layouts[i];
|
|
17351
|
-
if (offset < payloadStart || offset >= payloadEnd) {
|
|
17352
|
-
throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
|
|
17353
|
-
}
|
|
17354
|
-
return offset - payloadStart + payloadBases[i] + dataStart2;
|
|
17355
|
-
})
|
|
17356
|
-
)
|
|
17357
|
-
};
|
|
17358
|
-
const mediaDuration = sum(merged.durations);
|
|
17359
|
-
const shown = Math.round(
|
|
17360
|
-
(mediaDuration - track.mediaTime) * movieTimescale / track.timescale
|
|
17361
|
-
);
|
|
17362
|
-
const elst = Buffer.alloc(24);
|
|
17363
|
-
elst.writeUInt32BE(1);
|
|
17364
|
-
elst.writeBigUInt64BE(BigInt(shown), 4);
|
|
17365
|
-
elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
|
|
17366
|
-
elst.writeUInt32BE(65536, 20);
|
|
17367
|
-
const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
|
|
17368
|
-
const trak = rebuild(template, track.trak, {
|
|
17369
|
-
tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
|
|
17370
|
-
edts: () => Buffer.alloc(0),
|
|
17371
|
-
mdia: (mdia) => rebuild(template, mdia, {
|
|
17372
|
-
mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
|
|
17373
|
-
minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
|
|
17374
|
-
})
|
|
17375
|
-
});
|
|
17376
|
-
return { trak, shown };
|
|
17377
|
-
});
|
|
17378
|
-
let next = 0;
|
|
17379
|
-
return rebuild(template, root, {
|
|
17380
|
-
mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
|
|
17381
|
-
trak: () => traks[next++].trak
|
|
17382
|
-
});
|
|
17646
|
+
return {
|
|
17647
|
+
planDir: destinationDir,
|
|
17648
|
+
target,
|
|
17649
|
+
planHash: manifest.planHash,
|
|
17650
|
+
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
17651
|
+
artifactCount: artifacts.length,
|
|
17652
|
+
sizeBytes: artifacts.reduce((sum2, artifact) => sum2 + artifact.sizeBytes, 0),
|
|
17653
|
+
// Join the artifact's OWN name, not the current constant: a plan written
|
|
17654
|
+
// before the container change carries the legacy name, and materializing it
|
|
17655
|
+
// under the new one would point at a file that was never written.
|
|
17656
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join39(destinationDir, audioArtifact.path) : null : null
|
|
17383
17657
|
};
|
|
17384
|
-
const ftyp = layouts[0].ftyp;
|
|
17385
|
-
const dataStart = ftyp.length + buildMoov(0).length + 16;
|
|
17386
|
-
const mdat = Buffer.alloc(16);
|
|
17387
|
-
mdat.writeUInt32BE(1);
|
|
17388
|
-
mdat.write("mdat", 4, "latin1");
|
|
17389
|
-
mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
|
|
17390
|
-
return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
|
|
17391
17658
|
}
|
|
17392
|
-
function
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
return buf.subarray(0, bytesRead);
|
|
17399
|
-
} finally {
|
|
17400
|
-
await file.close();
|
|
17659
|
+
function assertRemoteSources(remoteSources, artifacts) {
|
|
17660
|
+
if (!isRecord3(remoteSources)) throw new PlanV2IntegrityError("remoteSources must be an object");
|
|
17661
|
+
for (const [path, url] of Object.entries(remoteSources)) {
|
|
17662
|
+
const artifact = artifacts.find((candidate) => candidate.path === path);
|
|
17663
|
+
if (!artifact || !isPlanV2DeferredSourceArtifact(artifact)) {
|
|
17664
|
+
throw new PlanV2IntegrityError(`remote source is not a selected deferred video: ${path}`);
|
|
17401
17665
|
}
|
|
17402
|
-
|
|
17666
|
+
if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
|
|
17667
|
+
throw new PlanV2IntegrityError(`remote source for ${path} must be an http(s) URL`);
|
|
17668
|
+
}
|
|
17669
|
+
}
|
|
17403
17670
|
}
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17671
|
+
function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
17672
|
+
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
17673
|
+
}
|
|
17674
|
+
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
17675
|
+
const markerPath = join39(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
17676
|
+
if (!existsSync25(markerPath)) return null;
|
|
17677
|
+
const marker = readJsonFile(markerPath, "materialization marker");
|
|
17678
|
+
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
17679
|
+
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
17680
|
+
}
|
|
17681
|
+
const target = marker.target;
|
|
17682
|
+
if (target.role !== expectedTarget.role || expectedTarget.role === "chunk" && target.chunkIndex !== expectedTarget.chunkIndex) {
|
|
17683
|
+
throw new PlanV2IntegrityError(
|
|
17684
|
+
"materialization target does not match requested execution role"
|
|
17411
17685
|
);
|
|
17412
17686
|
}
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
const
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
rmSync13(workDir, { recursive: true, force: true });
|
|
17687
|
+
const manifest = parsePlanV2Manifest(marker.manifest);
|
|
17688
|
+
const required = listPlanV2ArtifactsForTarget(manifest, expectedTarget);
|
|
17689
|
+
const remoteSources = marker.remoteSources ?? {};
|
|
17690
|
+
assertRemoteSources(remoteSources, required);
|
|
17691
|
+
for (const artifact of required) {
|
|
17692
|
+
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
17693
|
+
const path = join39(planDir, ...artifact.path.split("/"));
|
|
17694
|
+
if (!existsSync25(path) || statSync9(path).size !== artifact.sizeBytes) {
|
|
17695
|
+
throw new PlanV2IntegrityError(
|
|
17696
|
+
`materialized artifact missing or truncated: ${artifact.path}`
|
|
17697
|
+
);
|
|
17698
|
+
}
|
|
17699
|
+
if (sha256File(path) !== artifact.sha256) {
|
|
17700
|
+
throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
|
|
17701
|
+
}
|
|
17429
17702
|
}
|
|
17703
|
+
return { manifest, remoteSources };
|
|
17430
17704
|
}
|
|
17431
17705
|
|
|
17706
|
+
// ../producer/src/services/distributed/planV2Execution.ts
|
|
17707
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
|
|
17708
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
17709
|
+
import { join as join42 } from "path";
|
|
17710
|
+
|
|
17432
17711
|
// ../producer/src/services/distributed/assemble.ts
|
|
17712
|
+
import {
|
|
17713
|
+
cpSync as cpSync3,
|
|
17714
|
+
existsSync as existsSync26,
|
|
17715
|
+
mkdirSync as mkdirSync16,
|
|
17716
|
+
mkdtempSync as mkdtempSync8,
|
|
17717
|
+
readFileSync as readFileSync13,
|
|
17718
|
+
readdirSync as readdirSync10,
|
|
17719
|
+
renameSync as renameSync8,
|
|
17720
|
+
rmSync as rmSync14,
|
|
17721
|
+
statSync as statSync10,
|
|
17722
|
+
writeFileSync as writeFileSync12
|
|
17723
|
+
} from "fs";
|
|
17724
|
+
import { dirname as dirname16, join as join40 } from "path";
|
|
17433
17725
|
function cfrRateControlArgs(locked) {
|
|
17434
17726
|
const preset = getEncoderPreset(locked.quality ?? "standard", "mp4");
|
|
17435
17727
|
if (locked.crf == null && locked.bitrate) {
|
|
@@ -17456,8 +17748,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17456
17748
|
}
|
|
17457
17749
|
const format = plan2.dimensions.format;
|
|
17458
17750
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
17459
|
-
if (!
|
|
17460
|
-
|
|
17751
|
+
if (!existsSync26(dirname16(outputPath))) {
|
|
17752
|
+
mkdirSync16(dirname16(outputPath), { recursive: true });
|
|
17461
17753
|
}
|
|
17462
17754
|
const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
|
|
17463
17755
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -17479,8 +17771,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17479
17771
|
};
|
|
17480
17772
|
try {
|
|
17481
17773
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
17482
|
-
const finalPath =
|
|
17483
|
-
const audio = audioPath !== null &&
|
|
17774
|
+
const finalPath = join40(workDir, `output.${format}`);
|
|
17775
|
+
const audio = audioPath !== null && existsSync26(audioPath) ? audioPath : null;
|
|
17484
17776
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
17485
17777
|
renameSync8(finalPath, outputPath);
|
|
17486
17778
|
log.info("[assemble] stage timings", {
|
|
@@ -17498,7 +17790,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17498
17790
|
});
|
|
17499
17791
|
}
|
|
17500
17792
|
}
|
|
17501
|
-
const fileSize =
|
|
17793
|
+
const fileSize = existsSync26(outputPath) ? statSync10(outputPath).size : 0;
|
|
17502
17794
|
return {
|
|
17503
17795
|
outputPath,
|
|
17504
17796
|
durationMs: Date.now() - start,
|
|
@@ -17507,25 +17799,25 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17507
17799
|
};
|
|
17508
17800
|
}
|
|
17509
17801
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
17510
|
-
const planJsonPath =
|
|
17511
|
-
const chunksJsonPath =
|
|
17512
|
-
if (!
|
|
17802
|
+
const planJsonPath = join40(planDir, "plan.json");
|
|
17803
|
+
const chunksJsonPath = join40(planDir, "meta", "chunks.json");
|
|
17804
|
+
if (!existsSync26(planJsonPath)) {
|
|
17513
17805
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
17514
17806
|
}
|
|
17515
|
-
const plan2 = JSON.parse(
|
|
17807
|
+
const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
|
|
17516
17808
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
17517
17809
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
17518
|
-
if (!
|
|
17810
|
+
if (!existsSync26(chunksJsonPath)) {
|
|
17519
17811
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
17520
17812
|
}
|
|
17521
|
-
const chunks = JSON.parse(
|
|
17813
|
+
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
17522
17814
|
if (chunkPaths.length !== chunks.length) {
|
|
17523
17815
|
throw new Error(
|
|
17524
17816
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
17525
17817
|
);
|
|
17526
17818
|
}
|
|
17527
17819
|
for (const path of chunkPaths) {
|
|
17528
|
-
if (!
|
|
17820
|
+
if (!existsSync26(path)) {
|
|
17529
17821
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
17530
17822
|
}
|
|
17531
17823
|
}
|
|
@@ -17535,7 +17827,7 @@ function videoInputArgs(ctx, videoSources) {
|
|
|
17535
17827
|
return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
|
|
17536
17828
|
}
|
|
17537
17829
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
17538
|
-
const cfrOutputPath =
|
|
17830
|
+
const cfrOutputPath = join40(ctx.workDir, `cfr.${ctx.format}`);
|
|
17539
17831
|
const cfrArgs = [
|
|
17540
17832
|
...videoInputArgs(ctx, chunkPaths),
|
|
17541
17833
|
"-c:v",
|
|
@@ -17573,7 +17865,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
17573
17865
|
);
|
|
17574
17866
|
return;
|
|
17575
17867
|
}
|
|
17576
|
-
const paddedAudioPath =
|
|
17868
|
+
const paddedAudioPath = join40(ctx.workDir, "audio-padded.m4a");
|
|
17577
17869
|
const padTrimResult = await ctx.timed(
|
|
17578
17870
|
"audioMs",
|
|
17579
17871
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -17613,9 +17905,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
17613
17905
|
}
|
|
17614
17906
|
}
|
|
17615
17907
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
17616
|
-
const listPath =
|
|
17908
|
+
const listPath = join40(workDir, "concat-list.txt");
|
|
17617
17909
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
17618
|
-
|
|
17910
|
+
writeFileSync12(listPath, `${body}
|
|
17619
17911
|
`, "utf-8");
|
|
17620
17912
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
17621
17913
|
}
|
|
@@ -17646,9 +17938,9 @@ function chunksAreStitchedMp4(planDir, cfr) {
|
|
|
17646
17938
|
return true;
|
|
17647
17939
|
}
|
|
17648
17940
|
function chunksCarryAudio(planDir) {
|
|
17649
|
-
const encoderJsonPath =
|
|
17650
|
-
if (!
|
|
17651
|
-
return JSON.parse(
|
|
17941
|
+
const encoderJsonPath = join40(planDir, "meta", "encoder.json");
|
|
17942
|
+
if (!existsSync26(encoderJsonPath)) return false;
|
|
17943
|
+
return JSON.parse(readFileSync13(encoderJsonPath, "utf-8")).chunksCarryAudio === true;
|
|
17652
17944
|
}
|
|
17653
17945
|
function readCfrEncoder(planDir, format) {
|
|
17654
17946
|
if (format !== "mp4") {
|
|
@@ -17656,11 +17948,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
17656
17948
|
`[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
|
|
17657
17949
|
);
|
|
17658
17950
|
}
|
|
17659
|
-
const encoderJsonPath =
|
|
17660
|
-
if (!
|
|
17951
|
+
const encoderJsonPath = join40(planDir, "meta", "encoder.json");
|
|
17952
|
+
if (!existsSync26(encoderJsonPath)) {
|
|
17661
17953
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
17662
17954
|
}
|
|
17663
|
-
const encoderJson = JSON.parse(
|
|
17955
|
+
const encoderJson = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
|
|
17664
17956
|
if (encoderJson.encoder === "libx265-software") {
|
|
17665
17957
|
throw new Error(
|
|
17666
17958
|
`[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".`
|
|
@@ -17669,8 +17961,8 @@ function readCfrEncoder(planDir, format) {
|
|
|
17669
17961
|
return encoderJson;
|
|
17670
17962
|
}
|
|
17671
17963
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
17672
|
-
if (
|
|
17673
|
-
|
|
17964
|
+
if (existsSync26(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
|
|
17965
|
+
mkdirSync16(outputPath, { recursive: true });
|
|
17674
17966
|
let globalIdx = 0;
|
|
17675
17967
|
for (const chunkDir of chunkPaths) {
|
|
17676
17968
|
if (!statSync10(chunkDir).isDirectory()) {
|
|
@@ -17683,8 +17975,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17683
17975
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
17684
17976
|
}
|
|
17685
17977
|
for (const frame of frames) {
|
|
17686
|
-
const dst =
|
|
17687
|
-
cpSync3(
|
|
17978
|
+
const dst = join40(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
17979
|
+
cpSync3(join40(chunkDir, frame), dst);
|
|
17688
17980
|
globalIdx += 1;
|
|
17689
17981
|
}
|
|
17690
17982
|
}
|
|
@@ -17693,14 +17985,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17693
17985
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
17694
17986
|
);
|
|
17695
17987
|
}
|
|
17696
|
-
if (audioPath !== null &&
|
|
17697
|
-
const sidecar =
|
|
17988
|
+
if (audioPath !== null && existsSync26(audioPath)) {
|
|
17989
|
+
const sidecar = join40(outputPath, MIXED_AUDIO_FILENAME);
|
|
17698
17990
|
cpSync3(audioPath, sidecar);
|
|
17699
17991
|
}
|
|
17700
17992
|
let fileSize = 0;
|
|
17701
17993
|
for (const name of readdirSync10(outputPath)) {
|
|
17702
17994
|
try {
|
|
17703
|
-
fileSize += statSync10(
|
|
17995
|
+
fileSize += statSync10(join40(outputPath, name)).size;
|
|
17704
17996
|
} catch {
|
|
17705
17997
|
}
|
|
17706
17998
|
}
|
|
@@ -17714,105 +18006,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17714
18006
|
|
|
17715
18007
|
// ../producer/src/services/distributed/renderChunk.ts
|
|
17716
18008
|
import { randomBytes } from "crypto";
|
|
17717
|
-
import { existsSync as
|
|
17718
|
-
import { join as
|
|
17719
|
-
|
|
17720
|
-
// ../producer/src/services/distributed/chunkAudio.ts
|
|
17721
|
-
function audioCutForChunk(grid, chunks, index, fps) {
|
|
17722
|
-
const firstPacketAt = (frame) => Math.min(
|
|
17723
|
-
grid.packetCount,
|
|
17724
|
-
Math.max(
|
|
17725
|
-
0,
|
|
17726
|
-
Math.ceil(
|
|
17727
|
-
(frame * fps.den * grid.sampleRate - grid.firstStart * fps.num) / (grid.packetSamples * fps.num)
|
|
17728
|
-
)
|
|
17729
|
-
)
|
|
17730
|
-
);
|
|
17731
|
-
const first = index === 0 ? 0 : firstPacketAt(chunks[index].startFrame);
|
|
17732
|
-
const end = index === chunks.length - 1 ? grid.packetCount : firstPacketAt(chunks[index + 1].startFrame);
|
|
17733
|
-
if (end <= first) return null;
|
|
17734
|
-
const cutSeconds = (packet) => (grid.firstStart + packet * grid.packetSamples - grid.packetSamples / 2) / grid.sampleRate;
|
|
17735
|
-
const seekSeconds = index === 0 ? null : cutSeconds(first);
|
|
17736
|
-
const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
|
|
17737
|
-
return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
|
|
17738
|
-
}
|
|
17739
|
-
async function probeAacPacketGrid(audioPath) {
|
|
17740
|
-
const probe = await runFfprobeJson([
|
|
17741
|
-
"-v",
|
|
17742
|
-
"error",
|
|
17743
|
-
"-select_streams",
|
|
17744
|
-
"a:0",
|
|
17745
|
-
"-show_entries",
|
|
17746
|
-
"stream=sample_rate,time_base,nb_frames:packet=pts,duration",
|
|
17747
|
-
"-read_intervals",
|
|
17748
|
-
"%+#1",
|
|
17749
|
-
"-of",
|
|
17750
|
-
"json",
|
|
17751
|
-
"--",
|
|
17752
|
-
audioPath
|
|
17753
|
-
]);
|
|
17754
|
-
const stream = probe.streams?.[0] ?? {};
|
|
17755
|
-
const packet = probe.packets?.[0] ?? {};
|
|
17756
|
-
const sampleRate = Number(stream.sample_rate);
|
|
17757
|
-
const [tbNum = NaN, tbDen = NaN] = String(stream.time_base).split("/").map(Number);
|
|
17758
|
-
const toSamples = (ticks) => Number(ticks) * sampleRate * tbNum / tbDen;
|
|
17759
|
-
const grid = {
|
|
17760
|
-
sampleRate,
|
|
17761
|
-
firstStart: toSamples(packet.pts),
|
|
17762
|
-
packetSamples: toSamples(packet.duration),
|
|
17763
|
-
packetCount: Number(stream.nb_frames)
|
|
17764
|
-
};
|
|
17765
|
-
const counts = [grid.sampleRate, grid.packetSamples, grid.packetCount];
|
|
17766
|
-
if (!Number.isFinite(grid.firstStart) || !counts.every((n) => Number.isFinite(n) && n > 0)) {
|
|
17767
|
-
throw new Error(`[chunkAudio] could not read the AAC packet grid: ${JSON.stringify(probe)}`);
|
|
17768
|
-
}
|
|
17769
|
-
return grid;
|
|
17770
|
-
}
|
|
17771
|
-
function chunkMuxArgs(input, cut) {
|
|
17772
|
-
if (!cut) return ["-i", input.videoPath, "-map", "0:v:0"];
|
|
17773
|
-
const seek = cut.seekSeconds === null ? [] : ["-ss", cut.seekSeconds.toFixed(6)];
|
|
17774
|
-
const length = cut.durationSeconds === null ? [] : ["-t", cut.durationSeconds.toFixed(6)];
|
|
17775
|
-
const exact = seek.length ? ["-copypriorss", "0"] : [];
|
|
17776
|
-
return [
|
|
17777
|
-
...["-i", input.videoPath, ...seek, ...length, "-i", input.audioPath],
|
|
17778
|
-
...["-map", "0:v:0", "-map", "1:a:0", ...exact]
|
|
17779
|
-
];
|
|
17780
|
-
}
|
|
17781
|
-
async function muxChunkWithAudio(input) {
|
|
17782
|
-
const grid = input.audioPath ? await probeAacPacketGrid(input.audioPath) : null;
|
|
17783
|
-
const cut = grid ? audioCutForChunk(grid, input.chunks, input.chunkIndex, input.fps) : null;
|
|
17784
|
-
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
17785
|
-
appendRenderProvenanceArgs(args, input.outputPath);
|
|
17786
|
-
args.push("-y", input.outputPath);
|
|
17787
|
-
const result = await runFfmpeg(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
17788
|
-
if (!result.success) {
|
|
17789
|
-
throw new Error(
|
|
17790
|
-
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure(result, input.timeoutMs)}`
|
|
17791
|
-
);
|
|
17792
|
-
}
|
|
17793
|
-
const slice = input.chunks[input.chunkIndex];
|
|
17794
|
-
const expected = { video: slice.endFrame - slice.startFrame };
|
|
17795
|
-
if (cut) expected.audio = cut.packetCount;
|
|
17796
|
-
const probe = await runFfprobeJson([
|
|
17797
|
-
"-v",
|
|
17798
|
-
"error",
|
|
17799
|
-
"-count_packets",
|
|
17800
|
-
"-show_entries",
|
|
17801
|
-
"stream=codec_type,nb_read_packets",
|
|
17802
|
-
"-of",
|
|
17803
|
-
"json",
|
|
17804
|
-
"--",
|
|
17805
|
-
input.outputPath
|
|
17806
|
-
]);
|
|
17807
|
-
for (const [type, count] of Object.entries(expected)) {
|
|
17808
|
-
const packets = Number(probe.streams?.find((s) => s.codec_type === type)?.nb_read_packets ?? 0);
|
|
17809
|
-
if (packets !== count) {
|
|
17810
|
-
throw new Error(`[chunkAudio] ${type} packets in chunk ${packets}, expected ${count}`);
|
|
17811
|
-
}
|
|
17812
|
-
}
|
|
17813
|
-
}
|
|
17814
|
-
|
|
17815
|
-
// ../producer/src/services/distributed/renderChunk.ts
|
|
18009
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync14, readdirSync as readdirSync11, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
18010
|
+
import { join as join41 } from "path";
|
|
17816
18011
|
var LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
|
|
17817
18012
|
function validatePlanVideosForChunk(value) {
|
|
17818
18013
|
try {
|
|
@@ -17877,10 +18072,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
17877
18072
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
17878
18073
|
}
|
|
17879
18074
|
function hashChunkOutput(outputPath, kind) {
|
|
17880
|
-
if (kind === "file") return sha256Hex(
|
|
18075
|
+
if (kind === "file") return sha256Hex(readFileSync14(outputPath));
|
|
17881
18076
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
17882
18077
|
const lines = entries.map(
|
|
17883
|
-
(name) => `${name}\0${sha256Hex(
|
|
18078
|
+
(name) => `${name}\0${sha256Hex(readFileSync14(join41(outputPath, name)))}`
|
|
17884
18079
|
);
|
|
17885
18080
|
return sha256Hex(lines.join("\0"));
|
|
17886
18081
|
}
|
|
@@ -17897,37 +18092,39 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
17897
18092
|
async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
17898
18093
|
const start = Date.now();
|
|
17899
18094
|
const log = defaultLogger;
|
|
17900
|
-
const planJsonPath =
|
|
17901
|
-
const encoderJsonPath =
|
|
17902
|
-
const chunksJsonPath =
|
|
17903
|
-
if (!
|
|
18095
|
+
const planJsonPath = join41(planDir, "plan.json");
|
|
18096
|
+
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
18097
|
+
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
18098
|
+
if (!existsSync27(planJsonPath)) {
|
|
17904
18099
|
throw new RenderChunkValidationError(
|
|
17905
18100
|
MISSING_PLAN_ARTIFACT,
|
|
17906
18101
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
17907
18102
|
);
|
|
17908
18103
|
}
|
|
17909
|
-
const plan2 = JSON.parse(
|
|
18104
|
+
const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
|
|
17910
18105
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
17911
18106
|
const planHashStarted = Date.now();
|
|
17912
|
-
const
|
|
18107
|
+
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
17913
18108
|
role: "chunk",
|
|
17914
18109
|
chunkIndex
|
|
17915
18110
|
});
|
|
18111
|
+
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
18112
|
+
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
17916
18113
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
17917
|
-
if (!
|
|
18114
|
+
if (!existsSync27(required)) {
|
|
17918
18115
|
throw new RenderChunkValidationError(
|
|
17919
18116
|
MISSING_PLAN_ARTIFACT,
|
|
17920
18117
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
17921
18118
|
);
|
|
17922
18119
|
}
|
|
17923
18120
|
}
|
|
17924
|
-
const encoder = JSON.parse(
|
|
17925
|
-
const chunks = JSON.parse(
|
|
17926
|
-
const videosJsonPath =
|
|
18121
|
+
const encoder = JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
|
|
18122
|
+
const chunks = JSON.parse(readFileSync14(chunksJsonPath, "utf-8"));
|
|
18123
|
+
const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
17927
18124
|
let planVideos = null;
|
|
17928
|
-
if (
|
|
18125
|
+
if (existsSync27(videosJsonPath)) {
|
|
17929
18126
|
try {
|
|
17930
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
18127
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync14(videosJsonPath, "utf-8")));
|
|
17931
18128
|
} catch (err) {
|
|
17932
18129
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
17933
18130
|
throw new RenderChunkValidationError(
|
|
@@ -17953,8 +18150,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
17953
18150
|
`[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
|
|
17954
18151
|
);
|
|
17955
18152
|
}
|
|
17956
|
-
const compiledDir =
|
|
17957
|
-
if (!
|
|
18153
|
+
const compiledDir = join41(planDir, "compiled");
|
|
18154
|
+
if (!existsSync27(compiledDir)) {
|
|
17958
18155
|
throw new RenderChunkValidationError(
|
|
17959
18156
|
MISSING_PLAN_ARTIFACT,
|
|
17960
18157
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -18010,10 +18207,28 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18010
18207
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
18011
18208
|
};
|
|
18012
18209
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
18013
|
-
|
|
18014
|
-
const framesDir =
|
|
18015
|
-
|
|
18210
|
+
mkdirSync17(workDir, { recursive: true });
|
|
18211
|
+
const framesDir = join41(workDir, "captured-frames");
|
|
18212
|
+
mkdirSync17(framesDir, { recursive: true });
|
|
18016
18213
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
18214
|
+
const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
|
|
18215
|
+
planDir,
|
|
18216
|
+
planVideos,
|
|
18217
|
+
frames: Array.from(
|
|
18218
|
+
{ length: slice.endFrame - slice.startFrame },
|
|
18219
|
+
(_, i) => slice.startFrame + i
|
|
18220
|
+
),
|
|
18221
|
+
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
18222
|
+
outputRoot: videoFramesDir,
|
|
18223
|
+
cfg,
|
|
18224
|
+
remoteSources
|
|
18225
|
+
}) : [];
|
|
18226
|
+
const remoteVideoIds = new Set(
|
|
18227
|
+
planVideos?.extracted.filter(
|
|
18228
|
+
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
18229
|
+
).map((video) => video.videoId)
|
|
18230
|
+
);
|
|
18231
|
+
const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
|
|
18017
18232
|
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable(
|
|
18018
18233
|
planVideos.videos,
|
|
18019
18234
|
[
|
|
@@ -18022,17 +18237,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18022
18237
|
planVideos.extracted.filter((video) => !video.deferredRange),
|
|
18023
18238
|
v2Manifest === null ? "dense-v1" : "sparse-v2"
|
|
18024
18239
|
),
|
|
18025
|
-
...
|
|
18026
|
-
planDir,
|
|
18027
|
-
planVideos,
|
|
18028
|
-
frames: Array.from(
|
|
18029
|
-
{ length: slice.endFrame - slice.startFrame },
|
|
18030
|
-
(_, i) => slice.startFrame + i
|
|
18031
|
-
),
|
|
18032
|
-
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
18033
|
-
outputRoot: videoFramesDir,
|
|
18034
|
-
cfg
|
|
18035
|
-
})
|
|
18240
|
+
...deferredVideoFrames
|
|
18036
18241
|
],
|
|
18037
18242
|
resolveRenderFpsConfig(job.config.fps).value
|
|
18038
18243
|
) : null;
|
|
@@ -18050,7 +18255,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18050
18255
|
dimensions: plan2.dimensions,
|
|
18051
18256
|
encoder,
|
|
18052
18257
|
videoCount: planVideos?.videos.length ?? 0,
|
|
18053
|
-
html:
|
|
18258
|
+
html: readFileSync14(join41(compiledDir, "index.html"), "utf-8"),
|
|
18259
|
+
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
18260
|
+
skipReadinessVideoIds: deferredVideoFrames.filter((video) => video.framePaths.size === 0 || remoteVideoIds.has(video.videoId)).map((video) => video.videoId),
|
|
18261
|
+
videoMetadataHints: collectVideoMetadataHints(remoteVideos)
|
|
18054
18262
|
});
|
|
18055
18263
|
const chunkWorkerCount = calculateOptimalWorkers(framesInChunk, void 0, cfg);
|
|
18056
18264
|
let session = null;
|
|
@@ -18181,7 +18389,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18181
18389
|
},
|
|
18182
18390
|
resetForScreenshotRetry: () => {
|
|
18183
18391
|
rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
18184
|
-
|
|
18392
|
+
mkdirSync17(framesDir, { recursive: true });
|
|
18185
18393
|
capturePerfs.length = 0;
|
|
18186
18394
|
captureAttempts.length = 0;
|
|
18187
18395
|
job.framesRendered = 0;
|
|
@@ -18212,10 +18420,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18212
18420
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
18213
18421
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
18214
18422
|
if (isPngSequence) {
|
|
18215
|
-
if (!
|
|
18423
|
+
if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
18216
18424
|
} else {
|
|
18217
|
-
const outDir =
|
|
18218
|
-
if (!
|
|
18425
|
+
const outDir = join41(outputChunkPath, "..");
|
|
18426
|
+
if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
18219
18427
|
}
|
|
18220
18428
|
const encodeStarted = Date.now();
|
|
18221
18429
|
await runEncodeStage({
|
|
@@ -18258,9 +18466,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18258
18466
|
gopSize: framesInChunk
|
|
18259
18467
|
});
|
|
18260
18468
|
if (encoder.chunksCarryAudio) {
|
|
18469
|
+
const audioScratchPath = `${outputChunkPath}.audio.m4a`;
|
|
18261
18470
|
await muxChunkWithAudio({
|
|
18262
18471
|
videoPath: videoOnlyPath,
|
|
18263
|
-
audioPath:
|
|
18472
|
+
audioPath: chunkAudioSource(planDir, chunkIndex, audioScratchPath),
|
|
18264
18473
|
outputPath: outputChunkPath,
|
|
18265
18474
|
chunks,
|
|
18266
18475
|
chunkIndex,
|
|
@@ -18268,6 +18477,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18268
18477
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
18269
18478
|
});
|
|
18270
18479
|
rmSync15(videoOnlyPath, { force: true });
|
|
18480
|
+
rmSync15(audioScratchPath, { force: true });
|
|
18271
18481
|
}
|
|
18272
18482
|
encodeStageMs = Date.now() - encodeStarted;
|
|
18273
18483
|
} finally {
|
|
@@ -18306,7 +18516,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18306
18516
|
producerVersion: plan2.producerVersion,
|
|
18307
18517
|
ffmpegVersion
|
|
18308
18518
|
};
|
|
18309
|
-
|
|
18519
|
+
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
18310
18520
|
`, "utf-8");
|
|
18311
18521
|
try {
|
|
18312
18522
|
rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -18338,8 +18548,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18338
18548
|
|
|
18339
18549
|
// ../producer/src/services/distributed/planV2Execution.ts
|
|
18340
18550
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
|
|
18341
|
-
const workRoot = mkdtempSync9(
|
|
18342
|
-
const materializedPlanDir =
|
|
18551
|
+
const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
18552
|
+
const materializedPlanDir = join42(workRoot, "plan");
|
|
18343
18553
|
try {
|
|
18344
18554
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
18345
18555
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
|
|
@@ -18348,8 +18558,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
|
|
|
18348
18558
|
}
|
|
18349
18559
|
}
|
|
18350
18560
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
18351
|
-
const workRoot = mkdtempSync9(
|
|
18352
|
-
const materializedPlanDir =
|
|
18561
|
+
const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
18562
|
+
const materializedPlanDir = join42(workRoot, "plan");
|
|
18353
18563
|
try {
|
|
18354
18564
|
const materialized = materializePlanV2Target(
|
|
18355
18565
|
planV2Dir,
|
|
@@ -18369,9 +18579,9 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
18369
18579
|
}
|
|
18370
18580
|
|
|
18371
18581
|
// ../producer/src/services/distributed/projectHash.ts
|
|
18372
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
18582
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync15, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
|
|
18373
18583
|
import { createHash as createHash7 } from "crypto";
|
|
18374
|
-
import { join as
|
|
18584
|
+
import { join as join43 } from "path";
|
|
18375
18585
|
|
|
18376
18586
|
export {
|
|
18377
18587
|
createConsoleLogger,
|