hyperframes 0.8.178 → 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-C4GN5ZDP.js → add-2CCBND75.js} +5 -5
- package/dist/api.js +8 -8
- package/dist/{asset-WTZ44Q7G.js → asset-7HOLVSD5.js} +2 -2
- package/dist/{audio-ZSWOKO3D.js → audio-356GVOLJ.js} +5 -5
- package/dist/{auth-EUSWPXME.js → auth-YHI5VR4X.js} +1 -1
- package/dist/{autoUpdate-WWXRJ2W7.js → autoUpdate-OP345EBV.js} +2 -2
- package/dist/{backgroundChecks-B2IJHAP6.js → backgroundChecks-6NVELE25.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{beats-OJT75KQY.js → beats-DQH5SOHM.js} +4 -4
- package/dist/{benchmark-EZTB4ITW.js → benchmark-2W2NPNLJ.js} +6 -6
- package/dist/{browser-DNHBS5C7.js → browser-SCM75524.js} +3 -3
- package/dist/{bundleWithLocalizedFonts-GJAML42J.js → bundleWithLocalizedFonts-4F6HKLMZ.js} +1 -1
- package/dist/{catalog-YF23PXV2.js → catalog-MOSGL65T.js} +6 -6
- package/dist/{catch-up-YZZDUDRQ.js → catch-up-TCAZFTMO.js} +4 -4
- package/dist/{check-NVGQ5PMF.js → check-QVXSTHZB.js} +12 -12
- package/dist/{checkBrowser-JX22QQDH.js → checkBrowser-NF2CMWYR.js} +9 -9
- package/dist/chunk-2R5ZKWSV.js +26 -0
- package/dist/{chunk-HIP62XAW.js → chunk-2X4PQEYM.js} +1 -3
- package/dist/{chunk-HNMRB77F.js → chunk-5HZAIPHY.js} +1 -1
- package/dist/{chunk-DKGZR7QA.js → chunk-AEWZDKFS.js} +1 -1
- package/dist/{chunk-DNZMBQTH.js → chunk-AHPGAESX.js} +1 -1
- package/dist/{chunk-J4GE3BCG.js → chunk-BW2UQR4A.js} +4 -4
- package/dist/{chunk-Y6JLEN3P.js → chunk-C5XU53VP.js} +2 -2
- package/dist/{chunk-NL5F3AD4.js → chunk-CML25PAF.js} +2 -4
- package/dist/{chunk-U2AIXPAQ.js → chunk-COK5OUBX.js} +1 -1
- package/dist/{chunk-CJRGV2JW.js → chunk-CW3VIQMZ.js} +1 -1
- package/dist/{chunk-N6S6V2DY.js → chunk-DHKYJYOY.js} +1 -1
- package/dist/{chunk-C4FH67KI.js → chunk-DLF5KIBL.js} +2 -2
- package/dist/{chunk-FE2XTCTM.js → chunk-EEP5Y5IE.js} +1 -18
- package/dist/{chunk-YAPJOCSH.js → chunk-F3GML67X.js} +1 -1
- package/dist/{chunk-7IAVKLNC.js → chunk-HU4PBQGJ.js} +1 -1
- package/dist/{chunk-RTABHL2G.js → chunk-IHWI3KZY.js} +1 -1
- package/dist/{chunk-V6WIQWUB.js → chunk-ILLFG4RN.js} +2 -2
- package/dist/{chunk-O7PFPCQF.js → chunk-JRIXYXG3.js} +1334 -1252
- package/dist/{chunk-EBKIF46T.js → chunk-LBRJKTVY.js} +2 -2
- package/dist/{chunk-XBZOYP4D.js → chunk-M2DZX2DP.js} +1 -1
- package/dist/{chunk-G3M4BIFN.js → chunk-M5CJQ6EH.js} +1 -1
- package/dist/{chunk-74Z2T3YM.js → chunk-MK6D6HVT.js} +1 -1
- package/dist/{chunk-EINNFZAW.js → chunk-MXNKK7HY.js} +2 -2
- package/dist/{chunk-HFUMZZ4T.js → chunk-N3U7WHQK.js} +3 -3
- package/dist/{chunk-ZAO7FTQM.js → chunk-NTMCSIQB.js} +2 -2
- package/dist/{chunk-5PK3RLSB.js → chunk-OINJILMB.js} +1 -1
- package/dist/{chunk-62OXHKLS.js → chunk-OXP77SHD.js} +1 -1
- package/dist/{chunk-AR5MS234.js → chunk-Q73AGCTV.js} +3 -3
- package/dist/{chunk-AYEWZYYC.js → chunk-RJIJWETG.js} +1 -1
- package/dist/{chunk-UIXHQ4QG.js → chunk-VI7IQ2QF.js} +2 -2
- package/dist/{chunk-N66NVE65.js → chunk-WOXRCPJU.js} +1 -1
- package/dist/{chunk-PO2SDVQA.js → chunk-WQ6ZXHFQ.js} +3 -1
- package/dist/{chunk-UQEBSY5U.js → chunk-XX4ZYQR6.js} +1 -1
- package/dist/{clean-ODNP4ELD.js → clean-SJ2UIDKG.js} +4 -4
- package/dist/cli.js +52 -52
- package/dist/{cloud-2DACBBEP.js → cloud-DH7QGNPZ.js} +4 -4
- package/dist/{cloudrun-QSVGI2TT.js → cloudrun-ZA2ZH2VZ.js} +5 -3
- package/dist/{compare-HMMGOM4F.js → compare-5JNN5BHX.js} +4 -4
- package/dist/{component-C6OLXSIK.js → component-LJQWLOXZ.js} +3 -3
- package/dist/{compositions-MOGZ4ASK.js → compositions-UILNO2RF.js} +5 -5
- package/dist/{delete-MDAYHORQ.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/{doctor-VEKKYMQT.js → doctor-5G2RXBQX.js} +3 -3
- package/dist/{events-CSKJXRJU.js → events-2LHNVY2F.js} +3 -3
- package/dist/{events-SKMTX6HF.js → events-BXODXMJ6.js} +2 -2
- package/dist/{feedback-64AKUFHZ.js → feedback-57END2CW.js} +4 -4
- package/dist/{figma-YEY76ZPG.js → figma-O5MO4GF5.js} +3 -3
- package/dist/fontLocalizeCli.js +2 -2
- package/dist/{get-YJS76PGZ.js → get-OEWPJ4HH.js} +2 -2
- package/dist/{grade-compare-35UEA52Z.js → grade-compare-PG2UOMHO.js} +4 -4
- package/dist/{help-I5TCDUVH.js → help-44J4SQTE.js} +1 -1
- package/dist/{history-7BKIWSP2.js → history-CUA53AOE.js} +6 -6
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/{info-UEPOM2LV.js → info-UDXYDQVK.js} +5 -5
- package/dist/{init-HIB66K2S.js → init-CVT6HLW2.js} +16 -16
- package/dist/{inspect-634XIKDS.js → inspect-6F72RJQG.js} +6 -6
- package/dist/{keyframes-7BJX3EVE.js → keyframes-GJ7EEWW2.js} +5 -5
- package/dist/{lambda-Q4TRSMHZ.js → lambda-WFSBYU6U.js} +11 -16
- package/dist/{layout-6DUVSHEB.js → layout-SASENZOG.js} +6 -6
- package/dist/{lint-UPFD4JQZ.js → lint-JX57B52X.js} +6 -6
- package/dist/{list-M6OMQXJX.js → list-WG7NTE63.js} +2 -2
- package/dist/{login-QH6JDXDP.js → login-3NFLD65G.js} +4 -4
- package/dist/{manager-B4MSVFMK.js → manager-BKHRATMY.js} +2 -2
- package/dist/{media-treatment-SHFTIG4K.js → media-treatment-QI5F2SMU.js} +5 -5
- package/dist/{models-USEFHJFY.js → models-TXURF3J6.js} +4 -4
- package/dist/{normalize-audio-EP3HPQZB.js → normalize-audio-IEKL43N4.js} +4 -4
- package/dist/{open-HXZEXQ4X.js → open-GU3I75TB.js} +4 -4
- package/dist/{parakeetRunner-D5RSHQB2.js → parakeetRunner-S2QI3PCA.js} +5 -5
- package/dist/{pipeline-BDG52SPL.js → pipeline-OO2MYYIV.js} +2 -2
- package/dist/{play-LSRQ6R66.js → play-VL2WQCT3.js} +4 -4
- package/dist/{present-2VKAE63Q.js → present-YWSFX5CH.js} +4 -4
- package/dist/{preview-MWH5CHTN.js → preview-SMG6GXLY.js} +5 -5
- package/dist/{producer-W24FRIN7.js → producer-67RIQCWZ.js} +1 -1
- package/dist/{remove-background-JBOJAUIM.js → remove-background-HO2NBZCJ.js} +3 -3
- package/dist/{render-BROS2AIU.js → render-DH4CGGLO.js} +5 -5
- package/dist/{render-JCYFNDWT.js → render-MB7PW2YQ.js} +9 -9
- package/dist/{render-batch-FW56ZFGW.js → render-batch-4RPH66OX.js} +4 -1
- package/dist/{sam-IKPEJRRO.js → sam-MC24SZHC.js} +1 -1
- package/dist/{sherpa-GWYCXYEP.js → sherpa-LVXRZGE3.js} +4 -4
- package/dist/{sites-HZMADOA3.js → sites-3ZBYB5X4.js} +2 -1
- package/dist/{skills-YNM5SIHQ.js → skills-NDWQPEUD.js} +5 -5
- package/dist/{skillsUpdateCheck-FNC6VALY.js → skillsUpdateCheck-HZVZ3IUU.js} +3 -3
- package/dist/{snapshot-6EMEL4O3.js → snapshot-MCHWMAPH.js} +7 -7
- package/dist/{src-VUIKXEFU.js → src-E34ZBMAS.js} +1 -1
- package/dist/studio/assets/{hyperframes-player-Dfw61RJK.js → hyperframes-player-C0SyMC21.js} +1 -1
- package/dist/studio/assets/{index-eTRj5rY2.js → index-DHUassFP.js} +1 -1
- package/dist/studio/assets/{index-Bi6yVTq9.js → index-ueMRyAJ2.js} +150 -150
- package/dist/studio/index.html +1 -1
- package/dist/{studioServer-ZP2LJT7H.js → studioServer-XK5N6PIB.js} +8 -8
- package/dist/{synthesize-W2M7QGDV.js → synthesize-I2C24XJ5.js} +2 -2
- package/dist/{telemetry-HJQ7IO6R.js → telemetry-DD5ZIBOK.js} +3 -3
- package/dist/{timeline-CYW3YKMX.js → timeline-MJJEX5CU.js} +5 -5
- package/dist/{tokens-VOARLAAH.js → tokens-AKUZMYRE.js} +2 -2
- package/dist/{transcribe-F6UOG57Z.js → transcribe-ER7GGF4L.js} +3 -3
- package/dist/{transcribe-FJGNOR6R.js → transcribe-ZQI5P2LH.js} +9 -9
- package/dist/{tts-RE227GFP.js → tts-EDK3QS7M.js} +3 -3
- package/dist/{updateCheck-IOLCO3XV.js → updateCheck-GYFCXFWR.js} +2 -2
- package/dist/{upgrade-BVW67SB7.js → upgrade-UNWHMZP5.js} +2 -2
- package/dist/{validate-3NHIT4VT.js → validate-32CQZFNZ.js} +6 -6
- package/package.json +1 -1
- package/dist/deploy-3TTQZGTF.js +0 -116
|
@@ -13167,14 +13167,14 @@ async function executeRenderPipeline(input) {
|
|
|
13167
13167
|
import {
|
|
13168
13168
|
copyFileSync as copyFileSync4,
|
|
13169
13169
|
cpSync as cpSync2,
|
|
13170
|
-
existsSync as
|
|
13170
|
+
existsSync as existsSync20,
|
|
13171
13171
|
linkSync,
|
|
13172
|
-
mkdirSync as
|
|
13173
|
-
renameSync as
|
|
13174
|
-
rmSync as
|
|
13175
|
-
writeFileSync as
|
|
13172
|
+
mkdirSync as mkdirSync12,
|
|
13173
|
+
renameSync as renameSync5,
|
|
13174
|
+
rmSync as rmSync10,
|
|
13175
|
+
writeFileSync as writeFileSync8
|
|
13176
13176
|
} from "fs";
|
|
13177
|
-
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";
|
|
13178
13178
|
|
|
13179
13179
|
// ../producer/src/services/distributed/planFrameCost.ts
|
|
13180
13180
|
import { join as join28 } from "path";
|
|
@@ -14072,187 +14072,696 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
14072
14072
|
};
|
|
14073
14073
|
}
|
|
14074
14074
|
|
|
14075
|
-
// ../producer/src/services/distributed/
|
|
14076
|
-
import {
|
|
14077
|
-
import {
|
|
14078
|
-
import {
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
[PLAN_AUDIO_RELATIVE_PATH]: "audio",
|
|
14102
|
-
meta: "metadata",
|
|
14103
|
-
"plan.json": "metadata"
|
|
14104
|
-
};
|
|
14105
|
-
function hashComponent(value) {
|
|
14106
|
-
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 };
|
|
14107
14101
|
}
|
|
14108
|
-
function
|
|
14109
|
-
const
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
const videoKey = segments[1] ?? "unknown";
|
|
14113
|
-
return {
|
|
14114
|
-
category: "video-frames",
|
|
14115
|
-
componentLabel: `video-frames:${hashComponent(videoKey)}`
|
|
14116
|
-
};
|
|
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));
|
|
14117
14106
|
}
|
|
14118
|
-
|
|
14119
|
-
return { category, componentLabel: category };
|
|
14107
|
+
return boxes;
|
|
14120
14108
|
}
|
|
14121
|
-
function
|
|
14122
|
-
return
|
|
14123
|
-
totalBytes: 0,
|
|
14124
|
-
fileCount: 0,
|
|
14125
|
-
compiledBytes: 0,
|
|
14126
|
-
sourceMediaBytes: 0,
|
|
14127
|
-
videoFramesBytes: 0,
|
|
14128
|
-
videoFrameFileCount: 0,
|
|
14129
|
-
audioBytes: 0,
|
|
14130
|
-
metadataBytes: 0,
|
|
14131
|
-
otherBytes: 0,
|
|
14132
|
-
topComponents: []
|
|
14133
|
-
};
|
|
14109
|
+
function findChild(buf, parent, type) {
|
|
14110
|
+
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
14134
14111
|
}
|
|
14135
|
-
function
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
result.sourceMediaBytes += sizeBytes;
|
|
14140
|
-
}
|
|
14141
|
-
return;
|
|
14142
|
-
}
|
|
14143
|
-
if (category === "video-frames") {
|
|
14144
|
-
result.videoFramesBytes += sizeBytes;
|
|
14145
|
-
result.videoFrameFileCount += 1;
|
|
14146
|
-
return;
|
|
14147
|
-
}
|
|
14148
|
-
if (category === "audio") {
|
|
14149
|
-
result.audioBytes += sizeBytes;
|
|
14150
|
-
return;
|
|
14151
|
-
}
|
|
14152
|
-
if (category === "metadata") {
|
|
14153
|
-
result.metadataBytes += sizeBytes;
|
|
14154
|
-
return;
|
|
14155
|
-
}
|
|
14156
|
-
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;
|
|
14157
14116
|
}
|
|
14158
|
-
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
return;
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
14169
|
-
const classified = classifyFile(segments, rootKind);
|
|
14170
|
-
result.totalBytes += sizeBytes;
|
|
14171
|
-
result.fileCount += 1;
|
|
14172
|
-
addCategoryBytes(result, classified.category, filePath, sizeBytes);
|
|
14173
|
-
const component = components.get(classified.componentLabel) ?? {
|
|
14174
|
-
label: classified.componentLabel,
|
|
14175
|
-
sizeBytes: 0,
|
|
14176
|
-
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;
|
|
14177
14127
|
};
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
}
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
}
|
|
14189
|
-
for (const entry of entries) {
|
|
14190
|
-
const full = join30(dir, entry.name);
|
|
14191
|
-
if (entry.isDirectory()) {
|
|
14192
|
-
walkRegularFiles(full, visit);
|
|
14193
|
-
} else if (entry.isFile()) {
|
|
14194
|
-
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));
|
|
14195
14138
|
}
|
|
14139
|
+
at = box2.end;
|
|
14196
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 };
|
|
14197
14144
|
}
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
const
|
|
14201
|
-
|
|
14202
|
-
rootDir,
|
|
14203
|
-
(filePath) => recordFile(result, components, rootDir, rootKind, filePath)
|
|
14204
|
-
);
|
|
14205
|
-
result.topComponents = [...components.values()].sort((a, b) => b.sizeBytes - a.sizeBytes || a.label.localeCompare(b.label)).slice(0, MAX_TOP_COMPONENTS);
|
|
14206
|
-
return result;
|
|
14207
|
-
}
|
|
14208
|
-
|
|
14209
|
-
// ../producer/src/services/distributed/plan.ts
|
|
14210
|
-
function logPlanStage(log, stage, startedAt) {
|
|
14211
|
-
log.info("[plan] stage", { stage, ms: Date.now() - startedAt });
|
|
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));
|
|
14212
14149
|
}
|
|
14213
|
-
function
|
|
14214
|
-
const
|
|
14215
|
-
const
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
{
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
details: {
|
|
14223
|
-
mediaType: "audio",
|
|
14224
|
-
audioFailures: audioFailureDiagnostics(audioFailures),
|
|
14225
|
-
failureReasons: [...new Set(audioFailures.map((failure) => failure.reason))],
|
|
14226
|
-
failureStages: [...new Set(audioFailures.map((failure) => failure.stage))],
|
|
14227
|
-
failureOwner,
|
|
14228
|
-
retryable
|
|
14229
|
-
}
|
|
14230
|
-
}
|
|
14231
|
-
],
|
|
14232
|
-
log
|
|
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
|
+
)
|
|
14233
14159
|
);
|
|
14234
14160
|
}
|
|
14235
|
-
|
|
14236
|
-
"
|
|
14237
|
-
|
|
14238
|
-
"
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
var MAX_PARALLEL_CHUNKS_CEILING = 256;
|
|
14248
|
-
var ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME = 0.11;
|
|
14249
|
-
var ESTIMATED_CHUNK_OVERHEAD_SECONDS = 60;
|
|
14250
|
-
var WORKER_TIMEOUT_MARGIN = 0.75;
|
|
14251
|
-
function estimateFrameSeconds(megapixels, measuredSecondsPerFrame = 0) {
|
|
14252
|
-
return Math.max(ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels, measuredSecondsPerFrame);
|
|
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;
|
|
14253
14173
|
}
|
|
14254
|
-
function
|
|
14255
|
-
|
|
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);
|
|
14178
|
+
}
|
|
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",
|
|
14748
|
+
"output",
|
|
14749
|
+
"failures",
|
|
14750
|
+
"dist",
|
|
14751
|
+
".next",
|
|
14752
|
+
".turbo"
|
|
14753
|
+
]);
|
|
14754
|
+
var DEFAULT_MAX_PARALLEL_CHUNKS = 16;
|
|
14755
|
+
var MIN_CHUNK_SIZE = 10;
|
|
14756
|
+
var MAX_PARALLEL_CHUNKS_CEILING = 256;
|
|
14757
|
+
var ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME = 0.11;
|
|
14758
|
+
var ESTIMATED_CHUNK_OVERHEAD_SECONDS = 60;
|
|
14759
|
+
var WORKER_TIMEOUT_MARGIN = 0.75;
|
|
14760
|
+
function estimateFrameSeconds(megapixels, measuredSecondsPerFrame = 0) {
|
|
14761
|
+
return Math.max(ESTIMATED_SECONDS_PER_MEGAPIXEL_FRAME * megapixels, measuredSecondsPerFrame);
|
|
14762
|
+
}
|
|
14763
|
+
function estimateChunkSeconds(frames, megapixels, measuredSecondsPerFrame) {
|
|
14764
|
+
return ESTIMATED_CHUNK_OVERHEAD_SECONDS + frames * estimateFrameSeconds(megapixels, measuredSecondsPerFrame);
|
|
14256
14765
|
}
|
|
14257
14766
|
function planNeedsFrameCostMeasurement(html) {
|
|
14258
14767
|
return new RegExp(`\\b${HF_COLOR_GRADING_ATTR}=`, "i").test(html);
|
|
@@ -14397,13 +14906,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
14397
14906
|
}
|
|
14398
14907
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
14399
14908
|
"plan.json",
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14909
|
+
join33("meta", "composition.json"),
|
|
14910
|
+
join33("meta", "encoder.json"),
|
|
14911
|
+
join33("meta", "chunks.json")
|
|
14403
14912
|
];
|
|
14404
14913
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
14405
14914
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
14406
|
-
|
|
14915
|
+
rmSync10(join33(planDir, relativePath), { force: true });
|
|
14407
14916
|
}
|
|
14408
14917
|
}
|
|
14409
14918
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -14534,19 +15043,19 @@ function createVideoSourceShipper(input) {
|
|
|
14534
15043
|
const known = shipped.get(srcPath);
|
|
14535
15044
|
if (known) return known;
|
|
14536
15045
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
14537
|
-
const current = stagedRel === null ? srcPath :
|
|
15046
|
+
const current = stagedRel === null ? srcPath : join33(input.planDir, "video-frames", stagedRel);
|
|
14538
15047
|
let result = null;
|
|
14539
15048
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
14540
15049
|
const rel = pathInside(root, srcPath);
|
|
14541
|
-
if (rel !== null && (
|
|
15050
|
+
if (rel !== null && (existsSync20(join33(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
14542
15051
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
14543
15052
|
break;
|
|
14544
15053
|
}
|
|
14545
15054
|
}
|
|
14546
15055
|
if (result === null) {
|
|
14547
15056
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
14548
|
-
const destination =
|
|
14549
|
-
|
|
15057
|
+
const destination = join33(input.planDir, ...result.split("/"));
|
|
15058
|
+
mkdirSync12(join33(input.planDir, "video-sources"), { recursive: true });
|
|
14550
15059
|
try {
|
|
14551
15060
|
linkSync(current, destination);
|
|
14552
15061
|
} catch {
|
|
@@ -14564,7 +15073,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14564
15073
|
useGpu: false,
|
|
14565
15074
|
browserGpuMode: "software"
|
|
14566
15075
|
});
|
|
14567
|
-
if (!
|
|
15076
|
+
if (!existsSync20(planDir)) mkdirSync12(planDir, { recursive: true });
|
|
14568
15077
|
const log = config.logger ?? defaultLogger;
|
|
14569
15078
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
14570
15079
|
const abortSignal = config.abortSignal;
|
|
@@ -14593,15 +15102,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14593
15102
|
variables: config.variables
|
|
14594
15103
|
});
|
|
14595
15104
|
const entryFile = config.entryFile ?? "index.html";
|
|
14596
|
-
const htmlPath =
|
|
14597
|
-
if (!
|
|
15105
|
+
const htmlPath = join33(projectDir, entryFile);
|
|
15106
|
+
if (!existsSync20(htmlPath)) {
|
|
14598
15107
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
14599
15108
|
}
|
|
14600
|
-
const workDir =
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
const compiledDir =
|
|
14604
|
-
|
|
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 });
|
|
14605
15114
|
let stageStarted = Date.now();
|
|
14606
15115
|
cpSync2(projectDir, compiledDir, {
|
|
14607
15116
|
recursive: true,
|
|
@@ -14614,10 +15123,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14614
15123
|
}
|
|
14615
15124
|
});
|
|
14616
15125
|
logPlanStage(log, "copy", stageStarted);
|
|
14617
|
-
const finalCompiledDir =
|
|
15126
|
+
const finalCompiledDir = join33(planDir, "compiled");
|
|
14618
15127
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
14619
15128
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
14620
|
-
const remoteLocalPath = (rel) =>
|
|
15129
|
+
const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
|
|
14621
15130
|
const urlByAbsentLocalPath = new Map(
|
|
14622
15131
|
remoteFiles.map(([rel, file]) => [remoteLocalPath(rel), file.url])
|
|
14623
15132
|
);
|
|
@@ -14722,18 +15231,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14722
15231
|
if (extractResult.extractionResult) {
|
|
14723
15232
|
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
14724
15233
|
}
|
|
14725
|
-
const stagedVideoFrames =
|
|
14726
|
-
const videoFramesDst =
|
|
14727
|
-
if (
|
|
14728
|
-
const videoSourcesDir =
|
|
14729
|
-
|
|
14730
|
-
if (
|
|
14731
|
-
|
|
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);
|
|
14732
15241
|
} else {
|
|
14733
|
-
|
|
15242
|
+
mkdirSync12(videoFramesDst, { recursive: true });
|
|
14734
15243
|
}
|
|
14735
|
-
if (
|
|
14736
|
-
|
|
15244
|
+
if (existsSync20(finalCompiledDir)) rmSync10(finalCompiledDir, { recursive: true, force: true });
|
|
15245
|
+
renameSync5(compiledDir, finalCompiledDir);
|
|
14737
15246
|
const shipVideoSource = createVideoSourceShipper({
|
|
14738
15247
|
planDir,
|
|
14739
15248
|
projectDir,
|
|
@@ -14762,7 +15271,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14762
15271
|
};
|
|
14763
15272
|
})
|
|
14764
15273
|
});
|
|
14765
|
-
|
|
15274
|
+
mkdirSync12(join33(planDir, "meta"), { recursive: true });
|
|
14766
15275
|
writePlanRemoteFiles(
|
|
14767
15276
|
planDir,
|
|
14768
15277
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -14771,8 +15280,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14771
15280
|
sizeBytes
|
|
14772
15281
|
}))
|
|
14773
15282
|
);
|
|
14774
|
-
|
|
14775
|
-
|
|
15283
|
+
writeFileSync8(
|
|
15284
|
+
join33(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
14776
15285
|
JSON.stringify(planVideosJson, null, 2),
|
|
14777
15286
|
"utf-8"
|
|
14778
15287
|
);
|
|
@@ -14846,11 +15355,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14846
15355
|
if (audioResult.audioError) {
|
|
14847
15356
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
14848
15357
|
}
|
|
14849
|
-
const planAudioPath =
|
|
14850
|
-
if (audioResult.hasAudio &&
|
|
14851
|
-
|
|
15358
|
+
const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
15359
|
+
if (audioResult.hasAudio && existsSync20(audioResult.audioOutputPath)) {
|
|
15360
|
+
renameSync5(audioResult.audioOutputPath, planAudioPath);
|
|
14852
15361
|
}
|
|
14853
|
-
if (chunksCarryAudio2 &&
|
|
15362
|
+
if (chunksCarryAudio2 && existsSync20(planAudioPath)) {
|
|
14854
15363
|
stageStarted = Date.now();
|
|
14855
15364
|
const peak = await constrainAacTruePeak({
|
|
14856
15365
|
audioPath: planAudioPath,
|
|
@@ -14860,6 +15369,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14860
15369
|
});
|
|
14861
15370
|
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
14862
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
|
+
}
|
|
14863
15382
|
}
|
|
14864
15383
|
const ffmpegVersion = await readFfmpegVersion();
|
|
14865
15384
|
const producerVersion = readProducerVersion();
|
|
@@ -14893,7 +15412,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
14893
15412
|
format: config.format
|
|
14894
15413
|
};
|
|
14895
15414
|
try {
|
|
14896
|
-
|
|
15415
|
+
rmSync10(workDir, { recursive: true, force: true });
|
|
14897
15416
|
} catch (err) {
|
|
14898
15417
|
log.warn("[plan] failed to remove temp work dir", {
|
|
14899
15418
|
workDir,
|
|
@@ -15435,15 +15954,15 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
15435
15954
|
|
|
15436
15955
|
// ../producer/src/server.ts
|
|
15437
15956
|
import {
|
|
15438
|
-
existsSync as
|
|
15439
|
-
mkdirSync as
|
|
15957
|
+
existsSync as existsSync23,
|
|
15958
|
+
mkdirSync as mkdirSync13,
|
|
15440
15959
|
statSync as statSync7,
|
|
15441
|
-
mkdtempSync as
|
|
15442
|
-
writeFileSync as
|
|
15443
|
-
rmSync as
|
|
15444
|
-
createReadStream as
|
|
15960
|
+
mkdtempSync as mkdtempSync5,
|
|
15961
|
+
writeFileSync as writeFileSync9,
|
|
15962
|
+
rmSync as rmSync11,
|
|
15963
|
+
createReadStream as createReadStream5
|
|
15445
15964
|
} from "fs";
|
|
15446
|
-
import { resolve as resolve9, dirname as
|
|
15965
|
+
import { resolve as resolve9, dirname as dirname13, join as join36 } from "path";
|
|
15447
15966
|
import { tmpdir as tmpdir3 } from "os";
|
|
15448
15967
|
import { parseArgs } from "util";
|
|
15449
15968
|
import crypto from "crypto";
|
|
@@ -15453,15 +15972,15 @@ import { serve as serve2 } from "@hono/node-server";
|
|
|
15453
15972
|
|
|
15454
15973
|
// ../producer/src/services/hyperframeLint.ts
|
|
15455
15974
|
import {
|
|
15456
|
-
existsSync as
|
|
15457
|
-
readFileSync as
|
|
15975
|
+
existsSync as existsSync21,
|
|
15976
|
+
readFileSync as readFileSync11,
|
|
15458
15977
|
statSync as statSync6,
|
|
15459
|
-
openSync as
|
|
15978
|
+
openSync as openSync6,
|
|
15460
15979
|
fstatSync as fstatSync4,
|
|
15461
|
-
closeSync as
|
|
15980
|
+
closeSync as closeSync7,
|
|
15462
15981
|
constants as constants3
|
|
15463
15982
|
} from "fs";
|
|
15464
|
-
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";
|
|
15465
15984
|
function isStringRecord(value) {
|
|
15466
15985
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
15467
15986
|
return false;
|
|
@@ -15490,7 +16009,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
15490
16009
|
function readEntryHtml(filePath) {
|
|
15491
16010
|
let fd;
|
|
15492
16011
|
try {
|
|
15493
|
-
fd =
|
|
16012
|
+
fd = openSync6(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
|
|
15494
16013
|
} catch (error) {
|
|
15495
16014
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
15496
16015
|
return null;
|
|
@@ -15499,14 +16018,14 @@ function readEntryHtml(filePath) {
|
|
|
15499
16018
|
}
|
|
15500
16019
|
try {
|
|
15501
16020
|
if (!fstatSync4(fd).isFile()) return null;
|
|
15502
|
-
return
|
|
16021
|
+
return readFileSync11(fd, "utf-8");
|
|
15503
16022
|
} finally {
|
|
15504
|
-
|
|
16023
|
+
closeSync7(fd);
|
|
15505
16024
|
}
|
|
15506
16025
|
}
|
|
15507
16026
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15508
16027
|
const absProjectDir = resolve8(projectDir);
|
|
15509
|
-
if (!
|
|
16028
|
+
if (!existsSync21(absProjectDir) || !statSync6(absProjectDir).isDirectory()) {
|
|
15510
16029
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15511
16030
|
}
|
|
15512
16031
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15528,7 +16047,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
15528
16047
|
}
|
|
15529
16048
|
}
|
|
15530
16049
|
return {
|
|
15531
|
-
error: `No HTML entry file found in project directory: ${
|
|
16050
|
+
error: `No HTML entry file found in project directory: ${join34(absProjectDir, preferredEntryFile || "index.html")}`
|
|
15532
16051
|
};
|
|
15533
16052
|
}
|
|
15534
16053
|
function prepareHyperframeLintBody(body) {
|
|
@@ -15574,8 +16093,8 @@ async function runHyperframeLint(prepared) {
|
|
|
15574
16093
|
// ../producer/src/services/healthWorker.ts
|
|
15575
16094
|
import { Worker as Worker2 } from "worker_threads";
|
|
15576
16095
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
15577
|
-
import { dirname as
|
|
15578
|
-
import { existsSync as
|
|
16096
|
+
import { dirname as dirname12, join as join35 } from "path";
|
|
16097
|
+
import { existsSync as existsSync22 } from "fs";
|
|
15579
16098
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15580
16099
|
async function startHealthWorker(options = {}) {
|
|
15581
16100
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -15649,10 +16168,10 @@ function defaultLogger2() {
|
|
|
15649
16168
|
};
|
|
15650
16169
|
}
|
|
15651
16170
|
function resolveWorkerEntry2() {
|
|
15652
|
-
const here =
|
|
15653
|
-
const candidates = [
|
|
16171
|
+
const here = dirname12(fileURLToPath6(import.meta.url));
|
|
16172
|
+
const candidates = [join35(here, "healthWorkerThread.js"), join35(here, "healthWorkerThread.ts")];
|
|
15654
16173
|
for (const candidate of candidates) {
|
|
15655
|
-
if (
|
|
16174
|
+
if (existsSync22(candidate)) return candidate;
|
|
15656
16175
|
}
|
|
15657
16176
|
return void 0;
|
|
15658
16177
|
}
|
|
@@ -15790,8 +16309,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
15790
16309
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
15791
16310
|
const { input, cleanupProjectDir } = prepared;
|
|
15792
16311
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
15793
|
-
const outputDir =
|
|
15794
|
-
if (!
|
|
16312
|
+
const outputDir = dirname13(absoluteOutputPath);
|
|
16313
|
+
if (!existsSync23(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
15795
16314
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
15796
16315
|
}
|
|
15797
16316
|
function validateRenderOverrides(body) {
|
|
@@ -15831,11 +16350,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
15831
16350
|
const candidate = nonEmptyString(projectDir);
|
|
15832
16351
|
if (!candidate) return null;
|
|
15833
16352
|
const absProjectDir = resolve9(candidate);
|
|
15834
|
-
if (!
|
|
16353
|
+
if (!existsSync23(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
|
|
15835
16354
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15836
16355
|
}
|
|
15837
16356
|
const entry = options.entryFile || "index.html";
|
|
15838
|
-
if (!
|
|
16357
|
+
if (!existsSync23(resolve9(absProjectDir, entry))) {
|
|
15839
16358
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
15840
16359
|
}
|
|
15841
16360
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -15860,8 +16379,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
15860
16379
|
}
|
|
15861
16380
|
function materializeInlineProject(html, options) {
|
|
15862
16381
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
15863
|
-
const tempProjectDir =
|
|
15864
|
-
|
|
16382
|
+
const tempProjectDir = mkdtempSync5(join36(tempRoot, "producer-project-"));
|
|
16383
|
+
writeFileSync9(join36(tempProjectDir, "index.html"), html, "utf-8");
|
|
15865
16384
|
return {
|
|
15866
16385
|
prepared: {
|
|
15867
16386
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -15918,7 +16437,7 @@ function createArtifactStore(ttlMs) {
|
|
|
15918
16437
|
function cleanupTempDir(dir, log) {
|
|
15919
16438
|
if (!dir) return;
|
|
15920
16439
|
try {
|
|
15921
|
-
|
|
16440
|
+
rmSync11(dir, { recursive: true, force: true });
|
|
15922
16441
|
} catch (error) {
|
|
15923
16442
|
log.warn("Failed to cleanup temp project dir", {
|
|
15924
16443
|
cleanupProjectDir: dir,
|
|
@@ -15927,7 +16446,7 @@ function cleanupTempDir(dir, log) {
|
|
|
15927
16446
|
}
|
|
15928
16447
|
}
|
|
15929
16448
|
function outputFileSize(path) {
|
|
15930
|
-
return
|
|
16449
|
+
return existsSync23(path) ? statSync7(path).size : 0;
|
|
15931
16450
|
}
|
|
15932
16451
|
function createBlockingProgressReporter(log, requestId) {
|
|
15933
16452
|
let lastLoggedPct = -10;
|
|
@@ -16223,12 +16742,12 @@ function createRenderHandlers(options = {}) {
|
|
|
16223
16742
|
if (!artifact) {
|
|
16224
16743
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
16225
16744
|
}
|
|
16226
|
-
if (!
|
|
16745
|
+
if (!existsSync23(artifact.path)) {
|
|
16227
16746
|
store.delete(token);
|
|
16228
16747
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16229
16748
|
}
|
|
16230
16749
|
const stats = statSync7(artifact.path);
|
|
16231
|
-
return new Response(
|
|
16750
|
+
return new Response(createReadStream5(artifact.path), {
|
|
16232
16751
|
headers: {
|
|
16233
16752
|
"content-type": "video/mp4",
|
|
16234
16753
|
"content-length": String(stats.size),
|
|
@@ -16308,33 +16827,33 @@ if (isPublicServerEntry) {
|
|
|
16308
16827
|
// ../producer/src/services/distributed/planV2.ts
|
|
16309
16828
|
import {
|
|
16310
16829
|
copyFileSync as copyFileSync6,
|
|
16311
|
-
existsSync as
|
|
16830
|
+
existsSync as existsSync25,
|
|
16312
16831
|
lstatSync as lstatSync3,
|
|
16313
|
-
mkdirSync as
|
|
16314
|
-
mkdtempSync as
|
|
16315
|
-
readFileSync as
|
|
16316
|
-
readdirSync as readdirSync9,
|
|
16317
|
-
renameSync as
|
|
16318
|
-
rmSync as
|
|
16832
|
+
mkdirSync as mkdirSync15,
|
|
16833
|
+
mkdtempSync as mkdtempSync7,
|
|
16834
|
+
readFileSync as readFileSync12,
|
|
16835
|
+
readdirSync as readdirSync9,
|
|
16836
|
+
renameSync as renameSync7,
|
|
16837
|
+
rmSync as rmSync13,
|
|
16319
16838
|
statSync as statSync9,
|
|
16320
|
-
writeFileSync as
|
|
16839
|
+
writeFileSync as writeFileSync11
|
|
16321
16840
|
} from "fs";
|
|
16322
16841
|
import { tmpdir as tmpdir4 } from "os";
|
|
16323
|
-
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";
|
|
16324
16843
|
|
|
16325
16844
|
// ../producer/src/services/distributed/planV2Publisher.ts
|
|
16326
16845
|
import {
|
|
16327
16846
|
copyFileSync as copyFileSync5,
|
|
16328
|
-
existsSync as
|
|
16847
|
+
existsSync as existsSync24,
|
|
16329
16848
|
linkSync as linkSync2,
|
|
16330
|
-
mkdirSync as
|
|
16331
|
-
mkdtempSync as
|
|
16332
|
-
renameSync as
|
|
16333
|
-
rmSync as
|
|
16849
|
+
mkdirSync as mkdirSync14,
|
|
16850
|
+
mkdtempSync as mkdtempSync6,
|
|
16851
|
+
renameSync as renameSync6,
|
|
16852
|
+
rmSync as rmSync12,
|
|
16334
16853
|
statSync as statSync8,
|
|
16335
|
-
writeFileSync as
|
|
16854
|
+
writeFileSync as writeFileSync10
|
|
16336
16855
|
} from "fs";
|
|
16337
|
-
import { dirname as
|
|
16856
|
+
import { dirname as dirname14, join as join38 } from "path";
|
|
16338
16857
|
|
|
16339
16858
|
// ../producer/src/services/distributed/planV2Errors.ts
|
|
16340
16859
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -16349,7 +16868,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
16349
16868
|
};
|
|
16350
16869
|
|
|
16351
16870
|
// ../producer/src/services/distributed/planV2Layout.ts
|
|
16352
|
-
import { join as
|
|
16871
|
+
import { join as join37 } from "path";
|
|
16353
16872
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
16354
16873
|
function assertPlanV2Sha256(value, field) {
|
|
16355
16874
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -16359,7 +16878,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
16359
16878
|
}
|
|
16360
16879
|
function planV2BlobPath(root, sha256) {
|
|
16361
16880
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
16362
|
-
return
|
|
16881
|
+
return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
16363
16882
|
}
|
|
16364
16883
|
|
|
16365
16884
|
// ../producer/src/services/distributed/planV2Publisher.ts
|
|
@@ -16394,13 +16913,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16394
16913
|
#linkFile;
|
|
16395
16914
|
#committed = false;
|
|
16396
16915
|
constructor(destinationDir, options = {}) {
|
|
16397
|
-
if (
|
|
16916
|
+
if (existsSync24(destinationDir)) {
|
|
16398
16917
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
16399
16918
|
}
|
|
16400
16919
|
this.destinationDir = destinationDir;
|
|
16401
16920
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
16402
|
-
|
|
16403
|
-
this.temporaryDir =
|
|
16921
|
+
mkdirSync14(dirname14(destinationDir), { recursive: true });
|
|
16922
|
+
this.temporaryDir = mkdtempSync6(join38(dirname14(destinationDir), ".plan-v2-publish-"));
|
|
16404
16923
|
}
|
|
16405
16924
|
async putBlob(blob) {
|
|
16406
16925
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -16411,10 +16930,10 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16411
16930
|
);
|
|
16412
16931
|
}
|
|
16413
16932
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
16414
|
-
if (
|
|
16415
|
-
|
|
16416
|
-
const stagingDir =
|
|
16417
|
-
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");
|
|
16418
16937
|
try {
|
|
16419
16938
|
try {
|
|
16420
16939
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -16422,26 +16941,26 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
16422
16941
|
if (!canFallbackToCopy(error)) throw error;
|
|
16423
16942
|
copyFileSync5(blob.sourcePath, temporaryPath);
|
|
16424
16943
|
}
|
|
16425
|
-
|
|
16944
|
+
renameSync6(temporaryPath, destinationPath);
|
|
16426
16945
|
} finally {
|
|
16427
|
-
|
|
16946
|
+
rmSync12(stagingDir, { recursive: true, force: true });
|
|
16428
16947
|
}
|
|
16429
16948
|
}
|
|
16430
16949
|
async commitManifest(manifestBytes) {
|
|
16431
16950
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
16432
|
-
if (!
|
|
16951
|
+
if (!existsSync24(planV2BlobPath(this.temporaryDir, digest))) {
|
|
16433
16952
|
throw new PlanV2IntegrityError(
|
|
16434
16953
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
16435
16954
|
);
|
|
16436
16955
|
}
|
|
16437
16956
|
}
|
|
16438
|
-
|
|
16439
|
-
|
|
16957
|
+
writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
16958
|
+
renameSync6(this.temporaryDir, this.destinationDir);
|
|
16440
16959
|
this.#committed = true;
|
|
16441
16960
|
}
|
|
16442
16961
|
async abort() {
|
|
16443
16962
|
if (!this.#committed) {
|
|
16444
|
-
|
|
16963
|
+
rmSync12(this.temporaryDir, { recursive: true, force: true });
|
|
16445
16964
|
}
|
|
16446
16965
|
}
|
|
16447
16966
|
};
|
|
@@ -16460,7 +16979,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
16460
16979
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
16461
16980
|
}
|
|
16462
16981
|
function readJsonFile(path, label) {
|
|
16463
|
-
const contents =
|
|
16982
|
+
const contents = readFileSync12(path, "utf-8");
|
|
16464
16983
|
try {
|
|
16465
16984
|
const parsed = JSON.parse(contents);
|
|
16466
16985
|
return parsed;
|
|
@@ -16480,7 +16999,7 @@ function listFiles(root) {
|
|
|
16480
16999
|
const rootResolved = resolve10(root);
|
|
16481
17000
|
function walk(dir) {
|
|
16482
17001
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
16483
|
-
const absolutePath =
|
|
17002
|
+
const absolutePath = join39(dir, entry.name);
|
|
16484
17003
|
if (entry.isDirectory()) {
|
|
16485
17004
|
walk(absolutePath);
|
|
16486
17005
|
} else if (entry.isFile()) {
|
|
@@ -16507,13 +17026,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
16507
17026
|
}
|
|
16508
17027
|
}
|
|
16509
17028
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
16510
|
-
const videoRoot =
|
|
16511
|
-
if (!
|
|
17029
|
+
const videoRoot = join39(executionPlanDir, "video-frames");
|
|
17030
|
+
if (!existsSync25(videoRoot)) return;
|
|
16512
17031
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
16513
17032
|
if (!videoEntry.isDirectory()) continue;
|
|
16514
|
-
const videoDir =
|
|
17033
|
+
const videoDir = join39(videoRoot, videoEntry.name);
|
|
16515
17034
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
16516
|
-
const sentinelPath =
|
|
17035
|
+
const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
16517
17036
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
16518
17037
|
}
|
|
16519
17038
|
}
|
|
@@ -16530,10 +17049,13 @@ function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
|
16530
17049
|
}
|
|
16531
17050
|
return outputDir;
|
|
16532
17051
|
}
|
|
16533
|
-
function artifactTargets(path, videoDependencies,
|
|
17052
|
+
function artifactTargets(path, videoDependencies, chunkAudio) {
|
|
16534
17053
|
if (isPlanAudioArtifactPath(path)) {
|
|
16535
|
-
|
|
17054
|
+
if (chunkAudio === "none") return { chunks: [], assembler: true };
|
|
17055
|
+
return { chunks: chunkAudio === "whole" ? "all" : [], assembler: false };
|
|
16536
17056
|
}
|
|
17057
|
+
const slice = chunkAudioSliceTarget(path);
|
|
17058
|
+
if (slice !== null) return { chunks: slice === "all" ? "all" : [slice], assembler: false };
|
|
16537
17059
|
if (path === "plan.json" || path === "meta/chunks.json" || path === "meta/encoder.json") {
|
|
16538
17060
|
return { chunks: "all", assembler: true };
|
|
16539
17061
|
}
|
|
@@ -16557,7 +17079,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
16557
17079
|
if (video.deferredRange) {
|
|
16558
17080
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
16559
17081
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
16560
|
-
framePaths2.set(index,
|
|
17082
|
+
framePaths2.set(index, join39(outputDir, String(index)));
|
|
16561
17083
|
}
|
|
16562
17084
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
16563
17085
|
}
|
|
@@ -16565,7 +17087,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
16565
17087
|
const framePaths = /* @__PURE__ */ new Map();
|
|
16566
17088
|
for (const frameName of frameNames) {
|
|
16567
17089
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
16568
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
17090
|
+
assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
|
|
16569
17091
|
continue;
|
|
16570
17092
|
}
|
|
16571
17093
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -16584,7 +17106,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
16584
17106
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
16585
17107
|
);
|
|
16586
17108
|
}
|
|
16587
|
-
framePaths.set(frameIndex,
|
|
17109
|
+
framePaths.set(frameIndex, join39(outputDir, frameName));
|
|
16588
17110
|
}
|
|
16589
17111
|
return {
|
|
16590
17112
|
...video,
|
|
@@ -16605,11 +17127,11 @@ function parsePlanVideosJson2(value) {
|
|
|
16605
17127
|
}
|
|
16606
17128
|
}
|
|
16607
17129
|
function materializeExtractedVideoDirectories(planDir) {
|
|
16608
|
-
const videosPath =
|
|
16609
|
-
if (!
|
|
17130
|
+
const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
17131
|
+
if (!existsSync25(videosPath)) return;
|
|
16610
17132
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
16611
17133
|
for (const video of videos.extracted) {
|
|
16612
|
-
|
|
17134
|
+
mkdirSync15(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
16613
17135
|
}
|
|
16614
17136
|
}
|
|
16615
17137
|
function parseChunkSlices(value) {
|
|
@@ -16634,13 +17156,13 @@ function parseChunkSlices(value) {
|
|
|
16634
17156
|
});
|
|
16635
17157
|
}
|
|
16636
17158
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
16637
|
-
const videoRoot =
|
|
16638
|
-
const hasExtractedFrames =
|
|
16639
|
-
const videosPath =
|
|
16640
|
-
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)) {
|
|
16641
17163
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
16642
17164
|
}
|
|
16643
|
-
const chunksPath =
|
|
17165
|
+
const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
|
|
16644
17166
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
16645
17167
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
16646
17168
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -16680,871 +17202,526 @@ function collectChunkVideoDependencies(input) {
|
|
|
16680
17202
|
const path = input.deferredSources.get(videoId) ?? relative7(resolve10(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
16681
17203
|
assertSafeRelativePath(path);
|
|
16682
17204
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
16683
|
-
indexes.add(chunk.index);
|
|
16684
|
-
mutable.set(path, indexes);
|
|
16685
|
-
}
|
|
16686
|
-
}
|
|
16687
|
-
}
|
|
16688
|
-
return new Map([...mutable].map(([path, indexes]) => [path, [...indexes].sort((a, b) => a - b)]));
|
|
16689
|
-
}
|
|
16690
|
-
function manifestPayload(manifest) {
|
|
16691
|
-
return manifest;
|
|
16692
|
-
}
|
|
16693
|
-
function computeManifestHash(manifest) {
|
|
16694
|
-
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
16695
|
-
}
|
|
16696
|
-
function writeBlob(sourcePath, destinationPath) {
|
|
16697
|
-
if (existsSync24(destinationPath)) return;
|
|
16698
|
-
mkdirSync13(dirname14(destinationPath), { recursive: true });
|
|
16699
|
-
const temporaryDir = mkdtempSync6(join37(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
16700
|
-
const temporaryPath = join37(temporaryDir, "blob");
|
|
16701
|
-
try {
|
|
16702
|
-
copyFileSync6(sourcePath, temporaryPath);
|
|
16703
|
-
renameSync6(temporaryPath, destinationPath);
|
|
16704
|
-
} finally {
|
|
16705
|
-
rmSync12(temporaryDir, { recursive: true, force: true });
|
|
16706
|
-
}
|
|
16707
|
-
}
|
|
16708
|
-
function publishBlobFor(absolutePath, digests) {
|
|
16709
|
-
return {
|
|
16710
|
-
sourcePath: absolutePath,
|
|
16711
|
-
sha256: planFileSha256(absolutePath, digests),
|
|
16712
|
-
sizeBytes: statSync9(absolutePath).size
|
|
16713
|
-
};
|
|
16714
|
-
}
|
|
16715
|
-
async function putBlobs(publisher, blobs) {
|
|
16716
|
-
await putTasks(blobs.map((blob) => () => publisher.putBlob(blob)));
|
|
16717
|
-
}
|
|
16718
|
-
async function putTasks(tasks) {
|
|
16719
|
-
const uploadSlots = 32;
|
|
16720
|
-
let next = 0;
|
|
16721
|
-
let failure;
|
|
16722
|
-
const upload = async () => {
|
|
16723
|
-
while (!failure && next < tasks.length) {
|
|
16724
|
-
const task = tasks[next++];
|
|
16725
|
-
try {
|
|
16726
|
-
await task();
|
|
16727
|
-
} catch (reason) {
|
|
16728
|
-
failure ??= { reason };
|
|
16729
|
-
}
|
|
16730
|
-
}
|
|
16731
|
-
};
|
|
16732
|
-
await Promise.all(Array.from({ length: uploadSlots }, upload));
|
|
16733
|
-
if (failure) throw failure.reason;
|
|
16734
|
-
}
|
|
16735
|
-
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
16736
|
-
const executionPlanPath = join37(executionPlanDir, "plan.json");
|
|
16737
|
-
if (!existsSync24(executionPlanPath)) {
|
|
16738
|
-
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
16739
|
-
}
|
|
16740
|
-
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
16741
|
-
if (!isRecord3(executionPlanValue)) {
|
|
16742
|
-
throw new PlanV2IntegrityError("execution plan.json must be an object");
|
|
16743
|
-
}
|
|
16744
|
-
const executionPlan = executionPlanValue;
|
|
16745
|
-
readPlanProtocolV1(executionPlan);
|
|
16746
|
-
const executionPlanHash = executionPlan.planHash;
|
|
16747
|
-
if (!isSha256(executionPlanHash)) {
|
|
16748
|
-
throw new PlanV2IntegrityError("execution plan.json.planHash must be a sha256 digest");
|
|
16749
|
-
}
|
|
16750
|
-
const recomputedExecutionPlanHash = recomputePlanHashFromPlanDir(executionPlanDir, digests);
|
|
16751
|
-
if (recomputedExecutionPlanHash !== executionPlanHash) {
|
|
16752
|
-
throw new PlanV2IntegrityError(
|
|
16753
|
-
`execution plan content fingerprint does not match plan.json.planHash: expected ${executionPlanHash}, recomputed ${recomputedExecutionPlanHash}`
|
|
16754
|
-
);
|
|
16755
|
-
}
|
|
16756
|
-
const artifacts = [];
|
|
16757
|
-
const blobs = /* @__PURE__ */ new Map();
|
|
16758
|
-
const dimensions = executionPlan.dimensions;
|
|
16759
|
-
if (!isRecord3(dimensions)) {
|
|
16760
|
-
throw new PlanV2IntegrityError("execution plan.json.dimensions must be an object");
|
|
16761
|
-
}
|
|
16762
|
-
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
16763
|
-
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
16764
|
-
const encoder = readJsonFile(join37(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
16765
|
-
const chunksCarryAudio2 = isRecord3(encoder) && encoder.chunksCarryAudio === true;
|
|
16766
|
-
for (const file of listFiles(executionPlanDir)) {
|
|
16767
|
-
if (isExtractionCacheCompleteSentinelPath(file.path)) continue;
|
|
16768
|
-
const targets = artifactTargets(file.path, videoDependencyPlan.dependencies, chunksCarryAudio2);
|
|
16769
|
-
if ((file.path.startsWith("video-frames/") || file.path.startsWith("video-sources/")) && targets.chunks !== "all" && targets.chunks.length === 0 && !targets.assembler) {
|
|
16770
|
-
continue;
|
|
16771
|
-
}
|
|
16772
|
-
const blob = publishBlobFor(file.absolutePath, digests);
|
|
16773
|
-
if (!blobs.has(blob.sha256)) blobs.set(blob.sha256, blob);
|
|
16774
|
-
artifacts.push({
|
|
16775
|
-
path: file.path,
|
|
16776
|
-
sha256: blob.sha256,
|
|
16777
|
-
sizeBytes: blob.sizeBytes,
|
|
16778
|
-
...targets
|
|
16779
|
-
});
|
|
16780
|
-
}
|
|
16781
|
-
const storedBlobs = /* @__PURE__ */ new Map();
|
|
16782
|
-
for (const remote of readPlanRemoteFiles(executionPlanDir)) {
|
|
16783
|
-
const { sha256, sizeBytes } = remote;
|
|
16784
|
-
if (!blobs.has(sha256)) storedBlobs.set(sha256, { sha256, sizeBytes });
|
|
16785
|
-
artifacts.push({
|
|
16786
|
-
...remote,
|
|
16787
|
-
...artifactTargets(remote.path, videoDependencyPlan.dependencies, chunksCarryAudio2)
|
|
16788
|
-
});
|
|
16789
|
-
}
|
|
16790
|
-
const base = {
|
|
16791
|
-
protocol: PLAN_PROTOCOL_V2,
|
|
16792
|
-
// Retain the established manifest key byte-for-byte. It now acts as the
|
|
16793
|
-
// wire alias for the neutral local execution-plan hash.
|
|
16794
|
-
sourcePlanV1Hash: executionPlanHash,
|
|
16795
|
-
chunkCount: readPositiveInteger2(executionPlan.chunkCount, "chunkCount"),
|
|
16796
|
-
totalFrames: readPositiveInteger2(executionPlan.totalFrames, "totalFrames"),
|
|
16797
|
-
fps: readExecutionPlanFps(dimensions),
|
|
16798
|
-
width: readPositiveInteger2(dimensions.width, "dimensions.width"),
|
|
16799
|
-
height: readPositiveInteger2(dimensions.height, "dimensions.height"),
|
|
16800
|
-
format: readDistributedFormat(dimensions.format),
|
|
16801
|
-
ffmpegVersion: readString2(executionPlan.ffmpegVersion, "ffmpegVersion"),
|
|
16802
|
-
producerVersion: readString2(executionPlan.producerVersion, "producerVersion"),
|
|
16803
|
-
limitations: { videoDependencyMode: videoDependencyPlan.mode },
|
|
16804
|
-
artifacts
|
|
16805
|
-
};
|
|
16806
|
-
return {
|
|
16807
|
-
manifest: {
|
|
16808
|
-
...base,
|
|
16809
|
-
planHash: computeManifestHash(base)
|
|
16810
|
-
},
|
|
16811
|
-
blobs: [...blobs.values()],
|
|
16812
|
-
storedBlobs: [...storedBlobs.values()]
|
|
16813
|
-
};
|
|
16814
|
-
}
|
|
16815
|
-
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
16816
|
-
if (existsSync24(planV2Dir)) {
|
|
16817
|
-
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
16818
|
-
}
|
|
16819
|
-
const publication = buildPlanV2Publication(executionPlanDir);
|
|
16820
|
-
if (publication.storedBlobs.length > 0) {
|
|
16821
|
-
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
16822
|
-
}
|
|
16823
|
-
mkdirSync13(dirname14(planV2Dir), { recursive: true });
|
|
16824
|
-
const tempDir = mkdtempSync6(join37(dirname14(planV2Dir), ".plan-v2-build-"));
|
|
16825
|
-
try {
|
|
16826
|
-
for (const blob of publication.blobs) {
|
|
16827
|
-
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
16828
|
-
}
|
|
16829
|
-
writeFileSync10(
|
|
16830
|
-
join37(tempDir, "plan.json"),
|
|
16831
|
-
canonicalJsonStringify(publication.manifest),
|
|
16832
|
-
"utf-8"
|
|
16833
|
-
);
|
|
16834
|
-
renameSync6(tempDir, planV2Dir);
|
|
16835
|
-
return resultFromManifest(planV2Dir, publication.manifest);
|
|
16836
|
-
} catch (error) {
|
|
16837
|
-
rmSync12(tempDir, { recursive: true, force: true });
|
|
16838
|
-
throw error;
|
|
16839
|
-
}
|
|
16840
|
-
}
|
|
16841
|
-
function createPlanV2FromV1(executionPlanDir, planV2Dir) {
|
|
16842
|
-
return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
|
|
16843
|
-
}
|
|
16844
|
-
async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
|
|
16845
|
-
return publishExecutionPlan(executionPlanDir, publisher, {});
|
|
16846
|
-
}
|
|
16847
|
-
async function publishExecutionPlan(executionPlanDir, publisher, { digests, log = defaultLogger }) {
|
|
16848
|
-
try {
|
|
16849
|
-
let stageStarted = Date.now();
|
|
16850
|
-
const publication = buildPlanV2Publication(executionPlanDir, digests);
|
|
16851
|
-
logPlanStage(log, "publication", stageStarted);
|
|
16852
|
-
if (publication.storedBlobs.length > 0 && !publisher.referenceStoredBlob) {
|
|
16853
|
-
throw new PlanV2IntegrityError("this publisher cannot reference remote files");
|
|
16854
|
-
}
|
|
16855
|
-
stageStarted = Date.now();
|
|
16856
|
-
await putTasks([
|
|
16857
|
-
...publication.blobs.map((blob) => () => publisher.putBlob(blob)),
|
|
16858
|
-
...publication.storedBlobs.map((blob) => () => publisher.referenceStoredBlob(blob))
|
|
16859
|
-
]);
|
|
16860
|
-
logPlanStage(log, "upload", stageStarted);
|
|
16861
|
-
stageStarted = Date.now();
|
|
16862
|
-
await publisher.commitManifest(canonicalJsonStringify(publication.manifest));
|
|
16863
|
-
logPlanStage(log, "manifest", stageStarted);
|
|
16864
|
-
return publication.manifest;
|
|
16865
|
-
} catch (error) {
|
|
16866
|
-
try {
|
|
16867
|
-
await publisher.abort();
|
|
16868
|
-
} catch {
|
|
16869
|
-
}
|
|
16870
|
-
throw error;
|
|
16871
|
-
}
|
|
16872
|
-
}
|
|
16873
|
-
async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
16874
|
-
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
16875
|
-
}
|
|
16876
|
-
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
16877
|
-
const stagingRoot = mkdtempSync6(join37(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
16878
|
-
const digests = /* @__PURE__ */ new Map();
|
|
16879
|
-
try {
|
|
16880
|
-
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
16881
|
-
executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER,
|
|
16882
|
-
deferVideoExtraction: true,
|
|
16883
|
-
remoteFiles: options.remoteFiles,
|
|
16884
|
-
digests,
|
|
16885
|
-
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
16886
|
-
whileAudioMixes: () => putBlobs(
|
|
16887
|
-
publisher,
|
|
16888
|
-
listFiles(join37(stagingRoot, "compiled")).map(
|
|
16889
|
-
(file) => publishBlobFor(file.absolutePath, digests)
|
|
16890
|
-
)
|
|
16891
|
-
),
|
|
16892
|
-
chunksCarryAudio: options.chunksCarryAudio
|
|
16893
|
-
});
|
|
16894
|
-
return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
|
|
16895
|
-
} catch (error) {
|
|
16896
|
-
try {
|
|
16897
|
-
await publisher.abort();
|
|
16898
|
-
} catch {
|
|
16899
|
-
}
|
|
16900
|
-
throw error;
|
|
16901
|
-
} finally {
|
|
16902
|
-
rmSync12(stagingRoot, { recursive: true, force: true });
|
|
16903
|
-
}
|
|
16904
|
-
}
|
|
16905
|
-
async function planV2(projectDir, config, planV2Dir) {
|
|
16906
|
-
if (existsSync24(planV2Dir)) {
|
|
16907
|
-
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
16908
|
-
}
|
|
16909
|
-
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
16910
|
-
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
16911
|
-
stagingParentDir: dirname14(planV2Dir)
|
|
16912
|
-
});
|
|
16913
|
-
return resultFromManifest(planV2Dir, manifest);
|
|
16914
|
-
}
|
|
16915
|
-
function resultFromManifest(planV2Dir, manifest) {
|
|
16916
|
-
return {
|
|
16917
|
-
planDir: planV2Dir,
|
|
16918
|
-
manifestPath: join37(planV2Dir, "plan.json"),
|
|
16919
|
-
planProtocol: PLAN_PROTOCOL_V2,
|
|
16920
|
-
planHash: manifest.planHash,
|
|
16921
|
-
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
16922
|
-
chunkCount: manifest.chunkCount,
|
|
16923
|
-
totalFrames: manifest.totalFrames,
|
|
16924
|
-
fps: manifest.fps,
|
|
16925
|
-
width: manifest.width,
|
|
16926
|
-
height: manifest.height,
|
|
16927
|
-
format: manifest.format,
|
|
16928
|
-
ffmpegVersion: manifest.ffmpegVersion,
|
|
16929
|
-
producerVersion: manifest.producerVersion,
|
|
16930
|
-
limitations: manifest.limitations
|
|
16931
|
-
};
|
|
16932
|
-
}
|
|
16933
|
-
function getPlanV2ExecutionPlanHash(plan2) {
|
|
16934
|
-
return plan2.sourcePlanV1Hash;
|
|
16935
|
-
}
|
|
16936
|
-
function readString2(value, field) {
|
|
16937
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
16938
|
-
throw new PlanV2IntegrityError(`${field} must be a non-empty string`);
|
|
16939
|
-
}
|
|
16940
|
-
return value;
|
|
16941
|
-
}
|
|
16942
|
-
function readPositiveInteger2(value, field) {
|
|
16943
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
16944
|
-
throw new PlanV2IntegrityError(`${field} must be a positive integer`);
|
|
16945
|
-
}
|
|
16946
|
-
return value;
|
|
16947
|
-
}
|
|
16948
|
-
function readNonNegativeInteger2(value, field) {
|
|
16949
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
16950
|
-
throw new PlanV2IntegrityError(`${field} must be a non-negative integer`);
|
|
17205
|
+
indexes.add(chunk.index);
|
|
17206
|
+
mutable.set(path, indexes);
|
|
17207
|
+
}
|
|
17208
|
+
}
|
|
16951
17209
|
}
|
|
16952
|
-
return
|
|
17210
|
+
return new Map([...mutable].map(([path, indexes]) => [path, [...indexes].sort((a, b) => a - b)]));
|
|
16953
17211
|
}
|
|
16954
|
-
function
|
|
16955
|
-
|
|
16956
|
-
throw new PlanV2IntegrityError(
|
|
16957
|
-
`dimensions.fpsNum must be one of ${DISTRIBUTED_FPS.join(", ")}`
|
|
16958
|
-
);
|
|
16959
|
-
}
|
|
16960
|
-
return value;
|
|
17212
|
+
function manifestPayload(manifest) {
|
|
17213
|
+
return manifest;
|
|
16961
17214
|
}
|
|
16962
|
-
function
|
|
16963
|
-
|
|
16964
|
-
if (fpsDen !== 1) {
|
|
16965
|
-
throw new PlanV2IntegrityError("dimensions.fpsDen must be 1 for plan v2");
|
|
16966
|
-
}
|
|
16967
|
-
return readSupportedFps(dimensions.fpsNum);
|
|
17215
|
+
function computeManifestHash(manifest) {
|
|
17216
|
+
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
16968
17217
|
}
|
|
16969
|
-
function
|
|
16970
|
-
if (
|
|
16971
|
-
|
|
17218
|
+
function writeBlob(sourcePath, destinationPath) {
|
|
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");
|
|
17223
|
+
try {
|
|
17224
|
+
copyFileSync6(sourcePath, temporaryPath);
|
|
17225
|
+
renameSync7(temporaryPath, destinationPath);
|
|
17226
|
+
} finally {
|
|
17227
|
+
rmSync13(temporaryDir, { recursive: true, force: true });
|
|
16972
17228
|
}
|
|
16973
|
-
return value;
|
|
16974
17229
|
}
|
|
16975
|
-
function
|
|
16976
|
-
if (!isRecord3(value)) throw new PlanV2IntegrityError(`artifacts[${index}] must be an object`);
|
|
16977
|
-
const path = readString2(value.path, `artifacts[${index}].path`);
|
|
16978
|
-
assertSafeRelativePath(path);
|
|
16979
|
-
if (!isSha256(value.sha256)) {
|
|
16980
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].sha256 must be a lowercase sha256`);
|
|
16981
|
-
}
|
|
16982
|
-
const sizeBytes = value.sizeBytes;
|
|
16983
|
-
if (typeof sizeBytes !== "number" || !Number.isInteger(sizeBytes) || sizeBytes < 0) {
|
|
16984
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].sizeBytes must be a non-negative integer`);
|
|
16985
|
-
}
|
|
16986
|
-
let chunks;
|
|
16987
|
-
if (value.chunks === "all") {
|
|
16988
|
-
chunks = "all";
|
|
16989
|
-
} else if (isNonNegativeIntegerArray(value.chunks)) {
|
|
16990
|
-
chunks = value.chunks;
|
|
16991
|
-
} else {
|
|
16992
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].chunks is invalid`);
|
|
16993
|
-
}
|
|
16994
|
-
if (typeof value.assembler !== "boolean") {
|
|
16995
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].assembler must be boolean`);
|
|
16996
|
-
}
|
|
17230
|
+
function publishBlobFor(absolutePath, digests) {
|
|
16997
17231
|
return {
|
|
16998
|
-
|
|
16999
|
-
sha256:
|
|
17000
|
-
sizeBytes
|
|
17001
|
-
chunks,
|
|
17002
|
-
assembler: value.assembler
|
|
17003
|
-
};
|
|
17004
|
-
}
|
|
17005
|
-
function parsePlanV2Manifest(value) {
|
|
17006
|
-
if (!isRecord3(value)) throw new PlanV2IntegrityError("manifest must be an object");
|
|
17007
|
-
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) {
|
|
17008
|
-
throw new PlanV2IntegrityError("manifest protocol is not the supported v2 descriptor");
|
|
17009
|
-
}
|
|
17010
|
-
if (!isSha256(value.planHash) || !isSha256(value.sourcePlanV1Hash)) {
|
|
17011
|
-
throw new PlanV2IntegrityError("manifest hashes must be lowercase sha256 digests");
|
|
17012
|
-
}
|
|
17013
|
-
if (!Array.isArray(value.artifacts)) throw new PlanV2IntegrityError("artifacts must be an array");
|
|
17014
|
-
const artifacts = value.artifacts.map(parseArtifact);
|
|
17015
|
-
const paths = /* @__PURE__ */ new Set();
|
|
17016
|
-
for (const artifact of artifacts) {
|
|
17017
|
-
if (paths.has(artifact.path))
|
|
17018
|
-
throw new PlanV2IntegrityError(`duplicate artifact path: ${artifact.path}`);
|
|
17019
|
-
paths.add(artifact.path);
|
|
17020
|
-
}
|
|
17021
|
-
if (!paths.has("plan.json"))
|
|
17022
|
-
throw new PlanV2IntegrityError("manifest must include the local execution plan.json artifact");
|
|
17023
|
-
if (!isRecord3(value.limitations) || value.limitations.videoDependencyMode !== "exact-rendered-frames" && value.limitations.videoDependencyMode !== "full-source-pack" && value.limitations.videoDependencyMode !== "source-extract") {
|
|
17024
|
-
throw new PlanV2IntegrityError("unsupported video dependency mode");
|
|
17025
|
-
}
|
|
17026
|
-
const parsed = {
|
|
17027
|
-
protocol: PLAN_PROTOCOL_V2,
|
|
17028
|
-
planHash: value.planHash,
|
|
17029
|
-
sourcePlanV1Hash: value.sourcePlanV1Hash,
|
|
17030
|
-
chunkCount: readPositiveInteger2(value.chunkCount, "chunkCount"),
|
|
17031
|
-
totalFrames: readPositiveInteger2(value.totalFrames, "totalFrames"),
|
|
17032
|
-
fps: readSupportedFps(value.fps),
|
|
17033
|
-
width: readPositiveInteger2(value.width, "width"),
|
|
17034
|
-
height: readPositiveInteger2(value.height, "height"),
|
|
17035
|
-
format: readDistributedFormat(value.format),
|
|
17036
|
-
ffmpegVersion: readString2(value.ffmpegVersion, "ffmpegVersion"),
|
|
17037
|
-
producerVersion: readString2(value.producerVersion, "producerVersion"),
|
|
17038
|
-
limitations: { videoDependencyMode: value.limitations.videoDependencyMode },
|
|
17039
|
-
artifacts
|
|
17232
|
+
sourcePath: absolutePath,
|
|
17233
|
+
sha256: planFileSha256(absolutePath, digests),
|
|
17234
|
+
sizeBytes: statSync9(absolutePath).size
|
|
17040
17235
|
};
|
|
17041
|
-
const { planHash: _planHash, ...payload } = parsed;
|
|
17042
|
-
const expectedHash = computeManifestHash(payload);
|
|
17043
|
-
if (expectedHash !== parsed.planHash) {
|
|
17044
|
-
throw new PlanV2IntegrityError("manifest integrity hash mismatch");
|
|
17045
|
-
}
|
|
17046
|
-
for (const [index, artifact] of parsed.artifacts.entries()) {
|
|
17047
|
-
if (artifact.chunks !== "all" && artifact.chunks.some((chunkIndex) => chunkIndex >= parsed.chunkCount)) {
|
|
17048
|
-
throw new PlanV2IntegrityError(`artifacts[${index}].chunks contains an out-of-range index`);
|
|
17049
|
-
}
|
|
17050
|
-
}
|
|
17051
|
-
return parsed;
|
|
17052
17236
|
}
|
|
17053
|
-
function
|
|
17054
|
-
|
|
17055
|
-
if (!existsSync24(manifestPath)) {
|
|
17056
|
-
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
17057
|
-
}
|
|
17058
|
-
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
17059
|
-
return parsePlanV2Manifest(value);
|
|
17237
|
+
async function putBlobs(publisher, blobs) {
|
|
17238
|
+
await putTasks(blobs.map((blob) => () => publisher.putBlob(blob)));
|
|
17060
17239
|
}
|
|
17061
|
-
function
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17240
|
+
async function putTasks(tasks) {
|
|
17241
|
+
const uploadSlots = 32;
|
|
17242
|
+
let next = 0;
|
|
17243
|
+
let failure;
|
|
17244
|
+
const upload = async () => {
|
|
17245
|
+
while (!failure && next < tasks.length) {
|
|
17246
|
+
const task = tasks[next++];
|
|
17247
|
+
try {
|
|
17248
|
+
await task();
|
|
17249
|
+
} catch (reason) {
|
|
17250
|
+
failure ??= { reason };
|
|
17251
|
+
}
|
|
17065
17252
|
}
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17069
|
-
}
|
|
17070
|
-
return manifest.artifacts.filter((artifact) => artifact.assembler);
|
|
17253
|
+
};
|
|
17254
|
+
await Promise.all(Array.from({ length: uploadSlots }, upload));
|
|
17255
|
+
if (failure) throw failure.reason;
|
|
17071
17256
|
}
|
|
17072
|
-
function
|
|
17073
|
-
const
|
|
17074
|
-
if (!
|
|
17075
|
-
throw new PlanV2IntegrityError(`missing
|
|
17076
|
-
}
|
|
17077
|
-
const stats = statSync9(sourcePath);
|
|
17078
|
-
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
17079
|
-
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
17257
|
+
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
17258
|
+
const executionPlanPath = join39(executionPlanDir, "plan.json");
|
|
17259
|
+
if (!existsSync25(executionPlanPath)) {
|
|
17260
|
+
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
17080
17261
|
}
|
|
17081
|
-
|
|
17082
|
-
|
|
17262
|
+
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
17263
|
+
if (!isRecord3(executionPlanValue)) {
|
|
17264
|
+
throw new PlanV2IntegrityError("execution plan.json must be an object");
|
|
17083
17265
|
}
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
if (
|
|
17088
|
-
throw new PlanV2IntegrityError(
|
|
17266
|
+
const executionPlan = executionPlanValue;
|
|
17267
|
+
readPlanProtocolV1(executionPlan);
|
|
17268
|
+
const executionPlanHash = executionPlan.planHash;
|
|
17269
|
+
if (!isSha256(executionPlanHash)) {
|
|
17270
|
+
throw new PlanV2IntegrityError("execution plan.json.planHash must be a sha256 digest");
|
|
17089
17271
|
}
|
|
17090
|
-
const
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
const audioArtifact = artifacts.find((artifact) => isPlanAudioArtifactPath(artifact.path));
|
|
17095
|
-
const verified = artifacts.filter((artifact) => !Object.hasOwn(remoteSources, artifact.path)).map((artifact) => ({
|
|
17096
|
-
artifact,
|
|
17097
|
-
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
17098
|
-
}));
|
|
17099
|
-
mkdirSync13(dirname14(destinationDir), { recursive: true });
|
|
17100
|
-
const tempDir = mkdtempSync6(join37(dirname14(destinationDir), ".plan-v2-materialize-"));
|
|
17101
|
-
try {
|
|
17102
|
-
for (const { artifact, sourcePath } of verified) {
|
|
17103
|
-
const destinationPath = join37(tempDir, ...artifact.path.split("/"));
|
|
17104
|
-
mkdirSync13(dirname14(destinationPath), { recursive: true });
|
|
17105
|
-
copyFileSync6(sourcePath, destinationPath);
|
|
17106
|
-
}
|
|
17107
|
-
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
17108
|
-
writeFileSync10(
|
|
17109
|
-
join37(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
17110
|
-
canonicalJsonStringify({
|
|
17111
|
-
manifest,
|
|
17112
|
-
target,
|
|
17113
|
-
...Object.keys(remoteSources).length > 0 ? { remoteSources } : {}
|
|
17114
|
-
}),
|
|
17115
|
-
"utf-8"
|
|
17272
|
+
const recomputedExecutionPlanHash = recomputePlanHashFromPlanDir(executionPlanDir, digests);
|
|
17273
|
+
if (recomputedExecutionPlanHash !== executionPlanHash) {
|
|
17274
|
+
throw new PlanV2IntegrityError(
|
|
17275
|
+
`execution plan content fingerprint does not match plan.json.planHash: expected ${executionPlanHash}, recomputed ${recomputedExecutionPlanHash}`
|
|
17116
17276
|
);
|
|
17117
|
-
renameSync6(tempDir, destinationDir);
|
|
17118
|
-
} catch (error) {
|
|
17119
|
-
rmSync12(tempDir, { recursive: true, force: true });
|
|
17120
|
-
throw error;
|
|
17121
17277
|
}
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
const artifact = artifacts.find((candidate) => candidate.path === path);
|
|
17139
|
-
if (!artifact || !isPlanV2DeferredSourceArtifact(artifact)) {
|
|
17140
|
-
throw new PlanV2IntegrityError(`remote source is not a selected deferred video: ${path}`);
|
|
17141
|
-
}
|
|
17142
|
-
if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
|
|
17143
|
-
throw new PlanV2IntegrityError(`remote source for ${path} must be an http(s) URL`);
|
|
17278
|
+
const artifacts = [];
|
|
17279
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
17280
|
+
const dimensions = executionPlan.dimensions;
|
|
17281
|
+
if (!isRecord3(dimensions)) {
|
|
17282
|
+
throw new PlanV2IntegrityError("execution plan.json.dimensions must be an object");
|
|
17283
|
+
}
|
|
17284
|
+
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
17285
|
+
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
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) {
|
|
17290
|
+
if (isExtractionCacheCompleteSentinelPath(file.path)) continue;
|
|
17291
|
+
const targets = artifactTargets(file.path, videoDependencyPlan.dependencies, chunkAudio);
|
|
17292
|
+
if ((file.path.startsWith("video-frames/") || file.path.startsWith("video-sources/")) && targets.chunks !== "all" && targets.chunks.length === 0 && !targets.assembler) {
|
|
17293
|
+
continue;
|
|
17144
17294
|
}
|
|
17295
|
+
const blob = publishBlobFor(file.absolutePath, digests);
|
|
17296
|
+
if (!blobs.has(blob.sha256)) blobs.set(blob.sha256, blob);
|
|
17297
|
+
artifacts.push({
|
|
17298
|
+
path: file.path,
|
|
17299
|
+
sha256: blob.sha256,
|
|
17300
|
+
sizeBytes: blob.sizeBytes,
|
|
17301
|
+
...targets
|
|
17302
|
+
});
|
|
17145
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
|
+
}
|
|
17313
|
+
const base = {
|
|
17314
|
+
protocol: PLAN_PROTOCOL_V2,
|
|
17315
|
+
// Retain the established manifest key byte-for-byte. It now acts as the
|
|
17316
|
+
// wire alias for the neutral local execution-plan hash.
|
|
17317
|
+
sourcePlanV1Hash: executionPlanHash,
|
|
17318
|
+
chunkCount: readPositiveInteger2(executionPlan.chunkCount, "chunkCount"),
|
|
17319
|
+
totalFrames: readPositiveInteger2(executionPlan.totalFrames, "totalFrames"),
|
|
17320
|
+
fps: readExecutionPlanFps(dimensions),
|
|
17321
|
+
width: readPositiveInteger2(dimensions.width, "dimensions.width"),
|
|
17322
|
+
height: readPositiveInteger2(dimensions.height, "dimensions.height"),
|
|
17323
|
+
format: readDistributedFormat(dimensions.format),
|
|
17324
|
+
ffmpegVersion: readString2(executionPlan.ffmpegVersion, "ffmpegVersion"),
|
|
17325
|
+
producerVersion: readString2(executionPlan.producerVersion, "producerVersion"),
|
|
17326
|
+
limitations: { videoDependencyMode: videoDependencyPlan.mode },
|
|
17327
|
+
artifacts
|
|
17328
|
+
};
|
|
17329
|
+
return {
|
|
17330
|
+
manifest: {
|
|
17331
|
+
...base,
|
|
17332
|
+
planHash: computeManifestHash(base)
|
|
17333
|
+
},
|
|
17334
|
+
blobs: [...blobs.values()],
|
|
17335
|
+
storedBlobs: [...storedBlobs.values()]
|
|
17336
|
+
};
|
|
17146
17337
|
}
|
|
17147
|
-
function
|
|
17148
|
-
|
|
17149
|
-
}
|
|
17150
|
-
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
17151
|
-
const markerPath = join37(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
17152
|
-
if (!existsSync24(markerPath)) return null;
|
|
17153
|
-
const marker = readJsonFile(markerPath, "materialization marker");
|
|
17154
|
-
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
17155
|
-
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
17338
|
+
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
17339
|
+
if (existsSync25(planV2Dir)) {
|
|
17340
|
+
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
17156
17341
|
}
|
|
17157
|
-
const
|
|
17158
|
-
if (
|
|
17159
|
-
throw new PlanV2IntegrityError(
|
|
17160
|
-
"materialization target does not match requested execution role"
|
|
17161
|
-
);
|
|
17342
|
+
const publication = buildPlanV2Publication(executionPlanDir);
|
|
17343
|
+
if (publication.storedBlobs.length > 0) {
|
|
17344
|
+
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
17162
17345
|
}
|
|
17163
|
-
|
|
17164
|
-
const
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
17169
|
-
const path = join37(planDir, ...artifact.path.split("/"));
|
|
17170
|
-
if (!existsSync24(path) || statSync9(path).size !== artifact.sizeBytes) {
|
|
17171
|
-
throw new PlanV2IntegrityError(
|
|
17172
|
-
`materialized artifact missing or truncated: ${artifact.path}`
|
|
17173
|
-
);
|
|
17174
|
-
}
|
|
17175
|
-
if (sha256File(path) !== artifact.sha256) {
|
|
17176
|
-
throw new PlanV2IntegrityError(`materialized artifact hash mismatch: ${artifact.path}`);
|
|
17346
|
+
mkdirSync15(dirname15(planV2Dir), { recursive: true });
|
|
17347
|
+
const tempDir = mkdtempSync7(join39(dirname15(planV2Dir), ".plan-v2-build-"));
|
|
17348
|
+
try {
|
|
17349
|
+
for (const blob of publication.blobs) {
|
|
17350
|
+
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
17177
17351
|
}
|
|
17352
|
+
writeFileSync11(
|
|
17353
|
+
join39(tempDir, "plan.json"),
|
|
17354
|
+
canonicalJsonStringify(publication.manifest),
|
|
17355
|
+
"utf-8"
|
|
17356
|
+
);
|
|
17357
|
+
renameSync7(tempDir, planV2Dir);
|
|
17358
|
+
return resultFromManifest(planV2Dir, publication.manifest);
|
|
17359
|
+
} catch (error) {
|
|
17360
|
+
rmSync13(tempDir, { recursive: true, force: true });
|
|
17361
|
+
throw error;
|
|
17178
17362
|
}
|
|
17179
|
-
return { manifest, remoteSources };
|
|
17180
|
-
}
|
|
17181
|
-
|
|
17182
|
-
// ../producer/src/services/distributed/planV2Execution.ts
|
|
17183
|
-
import { mkdtempSync as mkdtempSync9, rmSync as rmSync16 } from "fs";
|
|
17184
|
-
import { tmpdir as tmpdir5 } from "os";
|
|
17185
|
-
import { join as join41 } from "path";
|
|
17186
|
-
|
|
17187
|
-
// ../producer/src/services/distributed/assemble.ts
|
|
17188
|
-
import {
|
|
17189
|
-
cpSync as cpSync3,
|
|
17190
|
-
existsSync as existsSync25,
|
|
17191
|
-
mkdirSync as mkdirSync15,
|
|
17192
|
-
mkdtempSync as mkdtempSync8,
|
|
17193
|
-
readFileSync as readFileSync12,
|
|
17194
|
-
readdirSync as readdirSync10,
|
|
17195
|
-
renameSync as renameSync8,
|
|
17196
|
-
rmSync as rmSync14,
|
|
17197
|
-
statSync as statSync10,
|
|
17198
|
-
writeFileSync as writeFileSync11
|
|
17199
|
-
} from "fs";
|
|
17200
|
-
import { dirname as dirname16, join as join39 } from "path";
|
|
17201
|
-
|
|
17202
|
-
// ../producer/src/services/distributed/mp4Stitch.ts
|
|
17203
|
-
import {
|
|
17204
|
-
createReadStream as createReadStream5,
|
|
17205
|
-
createWriteStream,
|
|
17206
|
-
mkdirSync as mkdirSync14,
|
|
17207
|
-
mkdtempSync as mkdtempSync7,
|
|
17208
|
-
renameSync as renameSync7,
|
|
17209
|
-
rmSync as rmSync13
|
|
17210
|
-
} from "fs";
|
|
17211
|
-
import { open } from "fs/promises";
|
|
17212
|
-
import { dirname as dirname15, join as join38 } from "path";
|
|
17213
|
-
import { finished, pipeline } from "stream/promises";
|
|
17214
|
-
function boxAt(src, off, at, limit) {
|
|
17215
|
-
let size = src.readUInt32BE(off);
|
|
17216
|
-
let bodyStart = at + 8;
|
|
17217
|
-
if (size === 1) {
|
|
17218
|
-
size = Number(src.readBigUInt64BE(off + 8));
|
|
17219
|
-
bodyStart = at + 16;
|
|
17220
|
-
}
|
|
17221
|
-
if (size < 8 || at + size > limit) throw new Error(`[mp4Stitch] malformed box at ${at}`);
|
|
17222
|
-
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
17223
|
-
}
|
|
17224
|
-
function childBoxes(buf, parent) {
|
|
17225
|
-
const boxes = [];
|
|
17226
|
-
for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
|
|
17227
|
-
boxes.push(boxAt(buf, at, at, parent.end));
|
|
17228
|
-
}
|
|
17229
|
-
return boxes;
|
|
17230
17363
|
}
|
|
17231
|
-
function
|
|
17232
|
-
return
|
|
17364
|
+
function createPlanV2FromV1(executionPlanDir, planV2Dir) {
|
|
17365
|
+
return createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir);
|
|
17233
17366
|
}
|
|
17234
|
-
function
|
|
17235
|
-
|
|
17236
|
-
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
17237
|
-
return found;
|
|
17367
|
+
async function publishPlanV2FromExecutionPlan(executionPlanDir, publisher) {
|
|
17368
|
+
return publishExecutionPlan(executionPlanDir, publisher, {});
|
|
17238
17369
|
}
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
if (start >= bufAt && end <= bufAt + buf.length) return;
|
|
17247
|
-
buf = await read(start, Math.max(end, start + SCAN_BYTES));
|
|
17248
|
-
bufAt = start;
|
|
17249
|
-
};
|
|
17250
|
-
for (let at = 0; !(found.has("ftyp") && found.has("moov") && payload); ) {
|
|
17251
|
-
await load(at, at + 16);
|
|
17252
|
-
if (at + 8 > bufAt + buf.length) {
|
|
17253
|
-
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");
|
|
17254
17377
|
}
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
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 {
|
|
17260
17392
|
}
|
|
17261
|
-
|
|
17393
|
+
throw error;
|
|
17262
17394
|
}
|
|
17263
|
-
const [ftyp, moov] = [found.get("ftyp"), found.get("moov")];
|
|
17264
|
-
if (moov.length !== moovSize(moov)) throw new Error("[mp4Stitch] moov is cut off");
|
|
17265
|
-
return { ftyp, moov, payloadStart: payload.bodyStart, payloadEnd: payload.end };
|
|
17266
|
-
}
|
|
17267
|
-
var moovSize = (moov) => boxAt(moov, 0, 0, Number.MAX_SAFE_INTEGER).end;
|
|
17268
|
-
function readTracks(moov) {
|
|
17269
|
-
const root = boxAt(moov, 0, 0, moov.length);
|
|
17270
|
-
return childBoxes(moov, root).filter((box2) => box2.type === "trak").map((trak) => readTrack(moov, trak));
|
|
17271
17395
|
}
|
|
17272
|
-
function
|
|
17273
|
-
|
|
17274
|
-
const first = table.bodyStart + countAt + 4;
|
|
17275
|
-
return Array.from(
|
|
17276
|
-
{ length: count },
|
|
17277
|
-
(_, row) => Array.from(
|
|
17278
|
-
{ length: columns },
|
|
17279
|
-
(_2, col) => buf.readUInt32BE(first + (row * columns + col) * 4)
|
|
17280
|
-
)
|
|
17281
|
-
);
|
|
17396
|
+
async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
17397
|
+
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
17282
17398
|
}
|
|
17283
|
-
function
|
|
17284
|
-
const
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
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 });
|
|
17293
17427
|
}
|
|
17294
|
-
return 0;
|
|
17295
|
-
}
|
|
17296
|
-
function readSizes(moov, stsz) {
|
|
17297
|
-
const fixedSize = moov.readUInt32BE(stsz.bodyStart + 4);
|
|
17298
|
-
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
17299
|
-
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
17300
|
-
}
|
|
17301
|
-
function readSync4(moov, stbl, sampleCount) {
|
|
17302
|
-
const stss = findChild(moov, stbl, "stss");
|
|
17303
|
-
if (!stss) return null;
|
|
17304
|
-
const sync = Array(sampleCount).fill(false);
|
|
17305
|
-
for (const [n] of rows(moov, stss, 1)) sync[n - 1] = true;
|
|
17306
|
-
return sync;
|
|
17307
|
-
}
|
|
17308
|
-
function readChunkOffsets(moov, stbl) {
|
|
17309
|
-
const stco = findChild(moov, stbl, "stco");
|
|
17310
|
-
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
17311
|
-
return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
17312
17428
|
}
|
|
17313
|
-
function
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
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)
|
|
17319
17436
|
});
|
|
17437
|
+
return resultFromManifest(planV2Dir, manifest);
|
|
17320
17438
|
}
|
|
17321
|
-
function
|
|
17322
|
-
const mdia = child(moov, trak, "mdia");
|
|
17323
|
-
const hdlr = child(moov, mdia, "hdlr");
|
|
17324
|
-
const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
|
|
17325
|
-
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
17326
|
-
const stts = rows(moov, child(moov, stbl, "stts"), 2);
|
|
17327
|
-
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
17328
|
-
const sizes = readSizes(moov, child(moov, stbl, "stsz"));
|
|
17329
|
-
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
17330
|
-
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
17331
|
-
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
17332
|
-
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
17333
|
-
}
|
|
17334
|
-
const stsd = child(moov, stbl, "stsd");
|
|
17439
|
+
function resultFromManifest(planV2Dir, manifest) {
|
|
17335
17440
|
return {
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
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
|
|
17346
17455
|
};
|
|
17347
17456
|
}
|
|
17348
|
-
function
|
|
17349
|
-
return
|
|
17457
|
+
function getPlanV2ExecutionPlanHash(plan2) {
|
|
17458
|
+
return plan2.sourcePlanV1Hash;
|
|
17350
17459
|
}
|
|
17351
|
-
function
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
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;
|
|
17356
17465
|
}
|
|
17357
|
-
function
|
|
17358
|
-
|
|
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;
|
|
17359
17471
|
}
|
|
17360
|
-
function
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
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;
|
|
17364
17477
|
}
|
|
17365
|
-
function
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
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
|
+
);
|
|
17371
17483
|
}
|
|
17372
|
-
return
|
|
17484
|
+
return value;
|
|
17373
17485
|
}
|
|
17374
|
-
function
|
|
17375
|
-
const
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
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);
|
|
17379
17492
|
}
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
function withDuration(buf, target, duration) {
|
|
17386
|
-
const copy = Buffer.from(buf.subarray(target.start, target.end));
|
|
17387
|
-
const body = target.bodyStart - target.start;
|
|
17388
|
-
const wide = copy[body] === 1;
|
|
17389
|
-
const at = body + DURATION_AT[target.type][wide ? 1 : 0];
|
|
17390
|
-
if (wide) copy.writeBigUInt64BE(BigInt(duration), at);
|
|
17391
|
-
else if (duration <= 4294967295) copy.writeUInt32BE(duration, at);
|
|
17392
|
-
else throw new Error(`[mp4Stitch] ${target.type} duration ${duration} needs a version 1 box`);
|
|
17393
|
-
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;
|
|
17394
17498
|
}
|
|
17395
|
-
function
|
|
17396
|
-
|
|
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
|
+
};
|
|
17397
17528
|
}
|
|
17398
|
-
function
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
for (const [count, perChunk] of runs(track.samplesPerChunk)) {
|
|
17403
|
-
stsc.push([firstChunk, perChunk, 1]);
|
|
17404
|
-
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");
|
|
17405
17533
|
}
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
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;
|
|
17419
17576
|
}
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
for (const track of tracks) {
|
|
17433
|
-
if (track.handler === "vide" && track.mediaTime !== 0) {
|
|
17434
|
-
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`);
|
|
17435
17589
|
}
|
|
17590
|
+
return manifest.artifacts.filter(
|
|
17591
|
+
(artifact) => artifact.chunks === "all" || artifact.chunks.some((chunkIndex) => chunkIndex === target.chunkIndex)
|
|
17592
|
+
);
|
|
17436
17593
|
}
|
|
17437
|
-
return
|
|
17594
|
+
return manifest.artifacts.filter((artifact) => artifact.assembler);
|
|
17438
17595
|
}
|
|
17439
|
-
function
|
|
17440
|
-
const
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
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;
|
|
17447
17645
|
}
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
sizes: parts.flatMap((part) => part.sizes),
|
|
17460
|
-
sync: parts.every((part) => !part.sync) ? null : parts.flatMap((part) => part.sync ?? part.sizes.map(() => true)),
|
|
17461
|
-
samplesPerChunk: parts.flatMap((part) => part.samplesPerChunk),
|
|
17462
|
-
chunkOffsets: parts.flatMap(
|
|
17463
|
-
(part) => part.chunkOffsets.map((offset) => {
|
|
17464
|
-
const i = part.chunk;
|
|
17465
|
-
const { payloadStart, payloadEnd } = layouts[i];
|
|
17466
|
-
if (offset < payloadStart || offset >= payloadEnd) {
|
|
17467
|
-
throw new Error(`[mp4Stitch] chunk ${i} has samples outside its mdat`);
|
|
17468
|
-
}
|
|
17469
|
-
return offset - payloadStart + payloadBases[i] + dataStart2;
|
|
17470
|
-
})
|
|
17471
|
-
)
|
|
17472
|
-
};
|
|
17473
|
-
const mediaDuration = sum(merged.durations);
|
|
17474
|
-
const shown = Math.round(
|
|
17475
|
-
(mediaDuration - track.mediaTime) * movieTimescale / track.timescale
|
|
17476
|
-
);
|
|
17477
|
-
const elst = Buffer.alloc(24);
|
|
17478
|
-
elst.writeUInt32BE(1);
|
|
17479
|
-
elst.writeBigUInt64BE(BigInt(shown), 4);
|
|
17480
|
-
elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
|
|
17481
|
-
elst.writeUInt32BE(65536, 20);
|
|
17482
|
-
const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
|
|
17483
|
-
const trak = rebuild(template, track.trak, {
|
|
17484
|
-
tkhd: (tkhd) => Buffer.concat([withDuration(template, tkhd, shown), edts]),
|
|
17485
|
-
edts: () => Buffer.alloc(0),
|
|
17486
|
-
mdia: (mdia) => rebuild(template, mdia, {
|
|
17487
|
-
mdhd: (mdhd) => withDuration(template, mdhd, mediaDuration),
|
|
17488
|
-
minf: (minf) => rebuild(template, minf, { stbl: () => buildStbl(track.stsd, merged) })
|
|
17489
|
-
})
|
|
17490
|
-
});
|
|
17491
|
-
return { trak, shown };
|
|
17492
|
-
});
|
|
17493
|
-
let next = 0;
|
|
17494
|
-
return rebuild(template, root, {
|
|
17495
|
-
mvhd: (mvhd) => withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown))),
|
|
17496
|
-
trak: () => traks[next++].trak
|
|
17497
|
-
});
|
|
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
|
|
17498
17657
|
};
|
|
17499
|
-
const ftyp = layouts[0].ftyp;
|
|
17500
|
-
const dataStart = ftyp.length + buildMoov(0).length + 16;
|
|
17501
|
-
const mdat = Buffer.alloc(16);
|
|
17502
|
-
mdat.writeUInt32BE(1);
|
|
17503
|
-
mdat.write("mdat", 4, "latin1");
|
|
17504
|
-
mdat.writeBigUInt64BE(BigInt(16 + payloadBytes), 8);
|
|
17505
|
-
return Buffer.concat([ftyp, buildMoov(dataStart), mdat]);
|
|
17506
17658
|
}
|
|
17507
|
-
function
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
return buf.subarray(0, bytesRead);
|
|
17514
|
-
} finally {
|
|
17515
|
-
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}`);
|
|
17516
17665
|
}
|
|
17517
|
-
|
|
17666
|
+
if (typeof url !== "string" || !/^https?:\/\//.test(url)) {
|
|
17667
|
+
throw new PlanV2IntegrityError(`remote source for ${path} must be an http(s) URL`);
|
|
17668
|
+
}
|
|
17669
|
+
}
|
|
17518
17670
|
}
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
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"
|
|
17526
17685
|
);
|
|
17527
17686
|
}
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
const
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
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
|
+
}
|
|
17544
17702
|
}
|
|
17703
|
+
return { manifest, remoteSources };
|
|
17545
17704
|
}
|
|
17546
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
|
+
|
|
17547
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";
|
|
17548
17725
|
function cfrRateControlArgs(locked) {
|
|
17549
17726
|
const preset = getEncoderPreset(locked.quality ?? "standard", "mp4");
|
|
17550
17727
|
if (locked.crf == null && locked.bitrate) {
|
|
@@ -17571,8 +17748,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17571
17748
|
}
|
|
17572
17749
|
const format = plan2.dimensions.format;
|
|
17573
17750
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
17574
|
-
if (!
|
|
17575
|
-
|
|
17751
|
+
if (!existsSync26(dirname16(outputPath))) {
|
|
17752
|
+
mkdirSync16(dirname16(outputPath), { recursive: true });
|
|
17576
17753
|
}
|
|
17577
17754
|
const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
|
|
17578
17755
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -17594,8 +17771,8 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17594
17771
|
};
|
|
17595
17772
|
try {
|
|
17596
17773
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
17597
|
-
const finalPath =
|
|
17598
|
-
const audio = audioPath !== null &&
|
|
17774
|
+
const finalPath = join40(workDir, `output.${format}`);
|
|
17775
|
+
const audio = audioPath !== null && existsSync26(audioPath) ? audioPath : null;
|
|
17599
17776
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
17600
17777
|
renameSync8(finalPath, outputPath);
|
|
17601
17778
|
log.info("[assemble] stage timings", {
|
|
@@ -17613,7 +17790,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17613
17790
|
});
|
|
17614
17791
|
}
|
|
17615
17792
|
}
|
|
17616
|
-
const fileSize =
|
|
17793
|
+
const fileSize = existsSync26(outputPath) ? statSync10(outputPath).size : 0;
|
|
17617
17794
|
return {
|
|
17618
17795
|
outputPath,
|
|
17619
17796
|
durationMs: Date.now() - start,
|
|
@@ -17622,25 +17799,25 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
|
|
|
17622
17799
|
};
|
|
17623
17800
|
}
|
|
17624
17801
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
17625
|
-
const planJsonPath =
|
|
17626
|
-
const chunksJsonPath =
|
|
17627
|
-
if (!
|
|
17802
|
+
const planJsonPath = join40(planDir, "plan.json");
|
|
17803
|
+
const chunksJsonPath = join40(planDir, "meta", "chunks.json");
|
|
17804
|
+
if (!existsSync26(planJsonPath)) {
|
|
17628
17805
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
17629
17806
|
}
|
|
17630
|
-
const plan2 = JSON.parse(
|
|
17807
|
+
const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
|
|
17631
17808
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
17632
17809
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
17633
|
-
if (!
|
|
17810
|
+
if (!existsSync26(chunksJsonPath)) {
|
|
17634
17811
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
17635
17812
|
}
|
|
17636
|
-
const chunks = JSON.parse(
|
|
17813
|
+
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
17637
17814
|
if (chunkPaths.length !== chunks.length) {
|
|
17638
17815
|
throw new Error(
|
|
17639
17816
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
17640
17817
|
);
|
|
17641
17818
|
}
|
|
17642
17819
|
for (const path of chunkPaths) {
|
|
17643
|
-
if (!
|
|
17820
|
+
if (!existsSync26(path)) {
|
|
17644
17821
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
17645
17822
|
}
|
|
17646
17823
|
}
|
|
@@ -17650,7 +17827,7 @@ function videoInputArgs(ctx, videoSources) {
|
|
|
17650
17827
|
return videoSources.length === 1 ? ["-i", videoSources[0]] : concatDemuxerInput(videoSources, ctx.workDir, ctx.fpsArg);
|
|
17651
17828
|
}
|
|
17652
17829
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
17653
|
-
const cfrOutputPath =
|
|
17830
|
+
const cfrOutputPath = join40(ctx.workDir, `cfr.${ctx.format}`);
|
|
17654
17831
|
const cfrArgs = [
|
|
17655
17832
|
...videoInputArgs(ctx, chunkPaths),
|
|
17656
17833
|
"-c:v",
|
|
@@ -17688,7 +17865,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
17688
17865
|
);
|
|
17689
17866
|
return;
|
|
17690
17867
|
}
|
|
17691
|
-
const paddedAudioPath =
|
|
17868
|
+
const paddedAudioPath = join40(ctx.workDir, "audio-padded.m4a");
|
|
17692
17869
|
const padTrimResult = await ctx.timed(
|
|
17693
17870
|
"audioMs",
|
|
17694
17871
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -17728,9 +17905,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
17728
17905
|
}
|
|
17729
17906
|
}
|
|
17730
17907
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
17731
|
-
const listPath =
|
|
17908
|
+
const listPath = join40(workDir, "concat-list.txt");
|
|
17732
17909
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
17733
|
-
|
|
17910
|
+
writeFileSync12(listPath, `${body}
|
|
17734
17911
|
`, "utf-8");
|
|
17735
17912
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
17736
17913
|
}
|
|
@@ -17761,9 +17938,9 @@ function chunksAreStitchedMp4(planDir, cfr) {
|
|
|
17761
17938
|
return true;
|
|
17762
17939
|
}
|
|
17763
17940
|
function chunksCarryAudio(planDir) {
|
|
17764
|
-
const encoderJsonPath =
|
|
17765
|
-
if (!
|
|
17766
|
-
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;
|
|
17767
17944
|
}
|
|
17768
17945
|
function readCfrEncoder(planDir, format) {
|
|
17769
17946
|
if (format !== "mp4") {
|
|
@@ -17771,11 +17948,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
17771
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.`
|
|
17772
17949
|
);
|
|
17773
17950
|
}
|
|
17774
|
-
const encoderJsonPath =
|
|
17775
|
-
if (!
|
|
17951
|
+
const encoderJsonPath = join40(planDir, "meta", "encoder.json");
|
|
17952
|
+
if (!existsSync26(encoderJsonPath)) {
|
|
17776
17953
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
17777
17954
|
}
|
|
17778
|
-
const encoderJson = JSON.parse(
|
|
17955
|
+
const encoderJson = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
|
|
17779
17956
|
if (encoderJson.encoder === "libx265-software") {
|
|
17780
17957
|
throw new Error(
|
|
17781
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".`
|
|
@@ -17784,8 +17961,8 @@ function readCfrEncoder(planDir, format) {
|
|
|
17784
17961
|
return encoderJson;
|
|
17785
17962
|
}
|
|
17786
17963
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
17787
|
-
if (
|
|
17788
|
-
|
|
17964
|
+
if (existsSync26(outputPath)) rmSync14(outputPath, { recursive: true, force: true });
|
|
17965
|
+
mkdirSync16(outputPath, { recursive: true });
|
|
17789
17966
|
let globalIdx = 0;
|
|
17790
17967
|
for (const chunkDir of chunkPaths) {
|
|
17791
17968
|
if (!statSync10(chunkDir).isDirectory()) {
|
|
@@ -17798,8 +17975,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17798
17975
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
17799
17976
|
}
|
|
17800
17977
|
for (const frame of frames) {
|
|
17801
|
-
const dst =
|
|
17802
|
-
cpSync3(
|
|
17978
|
+
const dst = join40(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
17979
|
+
cpSync3(join40(chunkDir, frame), dst);
|
|
17803
17980
|
globalIdx += 1;
|
|
17804
17981
|
}
|
|
17805
17982
|
}
|
|
@@ -17808,14 +17985,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17808
17985
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
17809
17986
|
);
|
|
17810
17987
|
}
|
|
17811
|
-
if (audioPath !== null &&
|
|
17812
|
-
const sidecar =
|
|
17988
|
+
if (audioPath !== null && existsSync26(audioPath)) {
|
|
17989
|
+
const sidecar = join40(outputPath, MIXED_AUDIO_FILENAME);
|
|
17813
17990
|
cpSync3(audioPath, sidecar);
|
|
17814
17991
|
}
|
|
17815
17992
|
let fileSize = 0;
|
|
17816
17993
|
for (const name of readdirSync10(outputPath)) {
|
|
17817
17994
|
try {
|
|
17818
|
-
fileSize += statSync10(
|
|
17995
|
+
fileSize += statSync10(join40(outputPath, name)).size;
|
|
17819
17996
|
} catch {
|
|
17820
17997
|
}
|
|
17821
17998
|
}
|
|
@@ -17829,105 +18006,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
17829
18006
|
|
|
17830
18007
|
// ../producer/src/services/distributed/renderChunk.ts
|
|
17831
18008
|
import { randomBytes } from "crypto";
|
|
17832
|
-
import { existsSync as
|
|
17833
|
-
import { join as
|
|
17834
|
-
|
|
17835
|
-
// ../producer/src/services/distributed/chunkAudio.ts
|
|
17836
|
-
function audioCutForChunk(grid, chunks, index, fps) {
|
|
17837
|
-
const firstPacketAt = (frame) => Math.min(
|
|
17838
|
-
grid.packetCount,
|
|
17839
|
-
Math.max(
|
|
17840
|
-
0,
|
|
17841
|
-
Math.ceil(
|
|
17842
|
-
(frame * fps.den * grid.sampleRate - grid.firstStart * fps.num) / (grid.packetSamples * fps.num)
|
|
17843
|
-
)
|
|
17844
|
-
)
|
|
17845
|
-
);
|
|
17846
|
-
const first = index === 0 ? 0 : firstPacketAt(chunks[index].startFrame);
|
|
17847
|
-
const end = index === chunks.length - 1 ? grid.packetCount : firstPacketAt(chunks[index + 1].startFrame);
|
|
17848
|
-
if (end <= first) return null;
|
|
17849
|
-
const cutSeconds = (packet) => (grid.firstStart + packet * grid.packetSamples - grid.packetSamples / 2) / grid.sampleRate;
|
|
17850
|
-
const seekSeconds = index === 0 ? null : cutSeconds(first);
|
|
17851
|
-
const durationSeconds = end === grid.packetCount ? null : cutSeconds(end) - (seekSeconds ?? 0);
|
|
17852
|
-
return { seekSeconds, durationSeconds, firstPacket: first, packetCount: end - first };
|
|
17853
|
-
}
|
|
17854
|
-
async function probeAacPacketGrid(audioPath) {
|
|
17855
|
-
const probe = await runFfprobeJson([
|
|
17856
|
-
"-v",
|
|
17857
|
-
"error",
|
|
17858
|
-
"-select_streams",
|
|
17859
|
-
"a:0",
|
|
17860
|
-
"-show_entries",
|
|
17861
|
-
"stream=sample_rate,time_base,nb_frames:packet=pts,duration",
|
|
17862
|
-
"-read_intervals",
|
|
17863
|
-
"%+#1",
|
|
17864
|
-
"-of",
|
|
17865
|
-
"json",
|
|
17866
|
-
"--",
|
|
17867
|
-
audioPath
|
|
17868
|
-
]);
|
|
17869
|
-
const stream = probe.streams?.[0] ?? {};
|
|
17870
|
-
const packet = probe.packets?.[0] ?? {};
|
|
17871
|
-
const sampleRate = Number(stream.sample_rate);
|
|
17872
|
-
const [tbNum = NaN, tbDen = NaN] = String(stream.time_base).split("/").map(Number);
|
|
17873
|
-
const toSamples = (ticks) => Number(ticks) * sampleRate * tbNum / tbDen;
|
|
17874
|
-
const grid = {
|
|
17875
|
-
sampleRate,
|
|
17876
|
-
firstStart: toSamples(packet.pts),
|
|
17877
|
-
packetSamples: toSamples(packet.duration),
|
|
17878
|
-
packetCount: Number(stream.nb_frames)
|
|
17879
|
-
};
|
|
17880
|
-
const counts = [grid.sampleRate, grid.packetSamples, grid.packetCount];
|
|
17881
|
-
if (!Number.isFinite(grid.firstStart) || !counts.every((n) => Number.isFinite(n) && n > 0)) {
|
|
17882
|
-
throw new Error(`[chunkAudio] could not read the AAC packet grid: ${JSON.stringify(probe)}`);
|
|
17883
|
-
}
|
|
17884
|
-
return grid;
|
|
17885
|
-
}
|
|
17886
|
-
function chunkMuxArgs(input, cut) {
|
|
17887
|
-
if (!cut) return ["-i", input.videoPath, "-map", "0:v:0"];
|
|
17888
|
-
const seek = cut.seekSeconds === null ? [] : ["-ss", cut.seekSeconds.toFixed(6)];
|
|
17889
|
-
const length = cut.durationSeconds === null ? [] : ["-t", cut.durationSeconds.toFixed(6)];
|
|
17890
|
-
const exact = seek.length ? ["-copypriorss", "0"] : [];
|
|
17891
|
-
return [
|
|
17892
|
-
...["-i", input.videoPath, ...seek, ...length, "-i", input.audioPath],
|
|
17893
|
-
...["-map", "0:v:0", "-map", "1:a:0", ...exact]
|
|
17894
|
-
];
|
|
17895
|
-
}
|
|
17896
|
-
async function muxChunkWithAudio(input) {
|
|
17897
|
-
const grid = input.audioPath ? await probeAacPacketGrid(input.audioPath) : null;
|
|
17898
|
-
const cut = grid ? audioCutForChunk(grid, input.chunks, input.chunkIndex, input.fps) : null;
|
|
17899
|
-
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
17900
|
-
appendRenderProvenanceArgs(args, input.outputPath);
|
|
17901
|
-
args.push("-y", input.outputPath);
|
|
17902
|
-
const result = await runFfmpeg(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
17903
|
-
if (!result.success) {
|
|
17904
|
-
throw new Error(
|
|
17905
|
-
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure(result, input.timeoutMs)}`
|
|
17906
|
-
);
|
|
17907
|
-
}
|
|
17908
|
-
const slice = input.chunks[input.chunkIndex];
|
|
17909
|
-
const expected = { video: slice.endFrame - slice.startFrame };
|
|
17910
|
-
if (cut) expected.audio = cut.packetCount;
|
|
17911
|
-
const probe = await runFfprobeJson([
|
|
17912
|
-
"-v",
|
|
17913
|
-
"error",
|
|
17914
|
-
"-count_packets",
|
|
17915
|
-
"-show_entries",
|
|
17916
|
-
"stream=codec_type,nb_read_packets",
|
|
17917
|
-
"-of",
|
|
17918
|
-
"json",
|
|
17919
|
-
"--",
|
|
17920
|
-
input.outputPath
|
|
17921
|
-
]);
|
|
17922
|
-
for (const [type, count] of Object.entries(expected)) {
|
|
17923
|
-
const packets = Number(probe.streams?.find((s) => s.codec_type === type)?.nb_read_packets ?? 0);
|
|
17924
|
-
if (packets !== count) {
|
|
17925
|
-
throw new Error(`[chunkAudio] ${type} packets in chunk ${packets}, expected ${count}`);
|
|
17926
|
-
}
|
|
17927
|
-
}
|
|
17928
|
-
}
|
|
17929
|
-
|
|
17930
|
-
// ../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";
|
|
17931
18011
|
var LEGACY_DISTRIBUTED_VP9_CPU_USED = 2;
|
|
17932
18012
|
function validatePlanVideosForChunk(value) {
|
|
17933
18013
|
try {
|
|
@@ -17992,10 +18072,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
17992
18072
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
17993
18073
|
}
|
|
17994
18074
|
function hashChunkOutput(outputPath, kind) {
|
|
17995
|
-
if (kind === "file") return sha256Hex(
|
|
18075
|
+
if (kind === "file") return sha256Hex(readFileSync14(outputPath));
|
|
17996
18076
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
17997
18077
|
const lines = entries.map(
|
|
17998
|
-
(name) => `${name}\0${sha256Hex(
|
|
18078
|
+
(name) => `${name}\0${sha256Hex(readFileSync14(join41(outputPath, name)))}`
|
|
17999
18079
|
);
|
|
18000
18080
|
return sha256Hex(lines.join("\0"));
|
|
18001
18081
|
}
|
|
@@ -18012,16 +18092,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
18012
18092
|
async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
18013
18093
|
const start = Date.now();
|
|
18014
18094
|
const log = defaultLogger;
|
|
18015
|
-
const planJsonPath =
|
|
18016
|
-
const encoderJsonPath =
|
|
18017
|
-
const chunksJsonPath =
|
|
18018
|
-
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)) {
|
|
18019
18099
|
throw new RenderChunkValidationError(
|
|
18020
18100
|
MISSING_PLAN_ARTIFACT,
|
|
18021
18101
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
18022
18102
|
);
|
|
18023
18103
|
}
|
|
18024
|
-
const plan2 = JSON.parse(
|
|
18104
|
+
const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
|
|
18025
18105
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
18026
18106
|
const planHashStarted = Date.now();
|
|
18027
18107
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -18031,20 +18111,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18031
18111
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
18032
18112
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
18033
18113
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
18034
|
-
if (!
|
|
18114
|
+
if (!existsSync27(required)) {
|
|
18035
18115
|
throw new RenderChunkValidationError(
|
|
18036
18116
|
MISSING_PLAN_ARTIFACT,
|
|
18037
18117
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
18038
18118
|
);
|
|
18039
18119
|
}
|
|
18040
18120
|
}
|
|
18041
|
-
const encoder = JSON.parse(
|
|
18042
|
-
const chunks = JSON.parse(
|
|
18043
|
-
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);
|
|
18044
18124
|
let planVideos = null;
|
|
18045
|
-
if (
|
|
18125
|
+
if (existsSync27(videosJsonPath)) {
|
|
18046
18126
|
try {
|
|
18047
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
18127
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync14(videosJsonPath, "utf-8")));
|
|
18048
18128
|
} catch (err) {
|
|
18049
18129
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
18050
18130
|
throw new RenderChunkValidationError(
|
|
@@ -18070,8 +18150,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18070
18150
|
`[renderChunk] chunk ${chunkIndex} has non-positive frame count: ${framesInChunk}`
|
|
18071
18151
|
);
|
|
18072
18152
|
}
|
|
18073
|
-
const compiledDir =
|
|
18074
|
-
if (!
|
|
18153
|
+
const compiledDir = join41(planDir, "compiled");
|
|
18154
|
+
if (!existsSync27(compiledDir)) {
|
|
18075
18155
|
throw new RenderChunkValidationError(
|
|
18076
18156
|
MISSING_PLAN_ARTIFACT,
|
|
18077
18157
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -18127,9 +18207,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18127
18207
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
18128
18208
|
};
|
|
18129
18209
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
18130
|
-
|
|
18131
|
-
const framesDir =
|
|
18132
|
-
|
|
18210
|
+
mkdirSync17(workDir, { recursive: true });
|
|
18211
|
+
const framesDir = join41(workDir, "captured-frames");
|
|
18212
|
+
mkdirSync17(framesDir, { recursive: true });
|
|
18133
18213
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
18134
18214
|
const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
|
|
18135
18215
|
planDir,
|
|
@@ -18175,7 +18255,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18175
18255
|
dimensions: plan2.dimensions,
|
|
18176
18256
|
encoder,
|
|
18177
18257
|
videoCount: planVideos?.videos.length ?? 0,
|
|
18178
|
-
html:
|
|
18258
|
+
html: readFileSync14(join41(compiledDir, "index.html"), "utf-8"),
|
|
18179
18259
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
18180
18260
|
skipReadinessVideoIds: deferredVideoFrames.filter((video) => video.framePaths.size === 0 || remoteVideoIds.has(video.videoId)).map((video) => video.videoId),
|
|
18181
18261
|
videoMetadataHints: collectVideoMetadataHints(remoteVideos)
|
|
@@ -18309,7 +18389,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18309
18389
|
},
|
|
18310
18390
|
resetForScreenshotRetry: () => {
|
|
18311
18391
|
rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
18312
|
-
|
|
18392
|
+
mkdirSync17(framesDir, { recursive: true });
|
|
18313
18393
|
capturePerfs.length = 0;
|
|
18314
18394
|
captureAttempts.length = 0;
|
|
18315
18395
|
job.framesRendered = 0;
|
|
@@ -18340,10 +18420,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18340
18420
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
18341
18421
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
18342
18422
|
if (isPngSequence) {
|
|
18343
|
-
if (!
|
|
18423
|
+
if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
18344
18424
|
} else {
|
|
18345
|
-
const outDir =
|
|
18346
|
-
if (!
|
|
18425
|
+
const outDir = join41(outputChunkPath, "..");
|
|
18426
|
+
if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
18347
18427
|
}
|
|
18348
18428
|
const encodeStarted = Date.now();
|
|
18349
18429
|
await runEncodeStage({
|
|
@@ -18386,9 +18466,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18386
18466
|
gopSize: framesInChunk
|
|
18387
18467
|
});
|
|
18388
18468
|
if (encoder.chunksCarryAudio) {
|
|
18469
|
+
const audioScratchPath = `${outputChunkPath}.audio.m4a`;
|
|
18389
18470
|
await muxChunkWithAudio({
|
|
18390
18471
|
videoPath: videoOnlyPath,
|
|
18391
|
-
audioPath:
|
|
18472
|
+
audioPath: chunkAudioSource(planDir, chunkIndex, audioScratchPath),
|
|
18392
18473
|
outputPath: outputChunkPath,
|
|
18393
18474
|
chunks,
|
|
18394
18475
|
chunkIndex,
|
|
@@ -18396,6 +18477,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18396
18477
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
18397
18478
|
});
|
|
18398
18479
|
rmSync15(videoOnlyPath, { force: true });
|
|
18480
|
+
rmSync15(audioScratchPath, { force: true });
|
|
18399
18481
|
}
|
|
18400
18482
|
encodeStageMs = Date.now() - encodeStarted;
|
|
18401
18483
|
} finally {
|
|
@@ -18434,7 +18516,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18434
18516
|
producerVersion: plan2.producerVersion,
|
|
18435
18517
|
ffmpegVersion
|
|
18436
18518
|
};
|
|
18437
|
-
|
|
18519
|
+
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
18438
18520
|
`, "utf-8");
|
|
18439
18521
|
try {
|
|
18440
18522
|
rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -18466,8 +18548,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
|
|
|
18466
18548
|
|
|
18467
18549
|
// ../producer/src/services/distributed/planV2Execution.ts
|
|
18468
18550
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
|
|
18469
|
-
const workRoot = mkdtempSync9(
|
|
18470
|
-
const materializedPlanDir =
|
|
18551
|
+
const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
18552
|
+
const materializedPlanDir = join42(workRoot, "plan");
|
|
18471
18553
|
try {
|
|
18472
18554
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
18473
18555
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath);
|
|
@@ -18476,8 +18558,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath) {
|
|
|
18476
18558
|
}
|
|
18477
18559
|
}
|
|
18478
18560
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
18479
|
-
const workRoot = mkdtempSync9(
|
|
18480
|
-
const materializedPlanDir =
|
|
18561
|
+
const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
18562
|
+
const materializedPlanDir = join42(workRoot, "plan");
|
|
18481
18563
|
try {
|
|
18482
18564
|
const materialized = materializePlanV2Target(
|
|
18483
18565
|
planV2Dir,
|
|
@@ -18497,9 +18579,9 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
18497
18579
|
}
|
|
18498
18580
|
|
|
18499
18581
|
// ../producer/src/services/distributed/projectHash.ts
|
|
18500
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
18582
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync15, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
|
|
18501
18583
|
import { createHash as createHash7 } from "crypto";
|
|
18502
|
-
import { join as
|
|
18584
|
+
import { join as join43 } from "path";
|
|
18503
18585
|
|
|
18504
18586
|
export {
|
|
18505
18587
|
createConsoleLogger,
|