@hyperframes/producer 0.8.205 → 0.8.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/distributed.js +745 -437
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +66 -64
- package/dist/index.d.ts +2 -0
- package/dist/index.js +818 -506
- package/dist/public-server.js +540 -230
- package/dist/services/animatedGifPrep.d.ts +6 -0
- package/dist/services/assetMediaType.d.ts +1 -0
- package/dist/services/heifImagePrep.d.ts +69 -0
- package/dist/services/heifRotation.d.ts +2 -0
- package/dist/services/htmlCompiler.d.ts +1 -4
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
4
|
existsSync as existsSync16,
|
|
5
|
-
mkdirSync as
|
|
6
|
-
readFileSync as
|
|
5
|
+
mkdirSync as mkdirSync10,
|
|
6
|
+
readFileSync as readFileSync9,
|
|
7
7
|
readdirSync as readdirSync5,
|
|
8
|
-
rmSync as
|
|
9
|
-
statSync as
|
|
10
|
-
writeFileSync as
|
|
11
|
-
copyFileSync as
|
|
8
|
+
rmSync as rmSync12,
|
|
9
|
+
statSync as statSync8,
|
|
10
|
+
writeFileSync as writeFileSync6,
|
|
11
|
+
copyFileSync as copyFileSync5,
|
|
12
12
|
appendFileSync
|
|
13
13
|
} from "fs";
|
|
14
14
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
augmentProtocolTimeoutError,
|
|
57
57
|
augmentPageNavigationTimeoutError
|
|
58
58
|
} from "@hyperframes/engine";
|
|
59
|
-
import { join as
|
|
59
|
+
import { join as join27, dirname as dirname11, resolve as resolve8 } from "path";
|
|
60
60
|
import { totalmem as totalmem2 } from "node:os";
|
|
61
61
|
import { randomUUID } from "crypto";
|
|
62
62
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -80,6 +80,7 @@ import { Readable } from "node:stream";
|
|
|
80
80
|
import { join, extname, resolve as resolve2, sep } from "node:path";
|
|
81
81
|
import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
|
|
82
82
|
import { fpsToNumber } from "@hyperframes/core";
|
|
83
|
+
import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
|
|
83
84
|
|
|
84
85
|
// src/services/hyperframeRuntimeLoader.ts
|
|
85
86
|
import { createHash } from "node:crypto";
|
|
@@ -196,11 +197,11 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
196
197
|
|
|
197
198
|
// src/services/fileServer.ts
|
|
198
199
|
var readFileAsync = promisify(readFile);
|
|
199
|
-
function isPathInside(
|
|
200
|
+
function isPathInside(child3, parent, options = {}) {
|
|
200
201
|
const { resolveSymlinks = false, pathModule } = options;
|
|
201
202
|
const resolveFn = pathModule?.resolve ?? resolve2;
|
|
202
203
|
const separator = pathModule?.sep ?? sep;
|
|
203
|
-
const resolvedChild = resolveFn(
|
|
204
|
+
const resolvedChild = resolveFn(child3);
|
|
204
205
|
const resolvedParent = resolveFn(parent);
|
|
205
206
|
const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
|
|
206
207
|
const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
|
|
@@ -720,6 +721,18 @@ function createFileServer(options) {
|
|
|
720
721
|
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
721
722
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
722
723
|
}
|
|
724
|
+
if (ext === ".png") {
|
|
725
|
+
const untagged = stripSdrPngColourTags(await readFileAsync(fd));
|
|
726
|
+
if (untagged) {
|
|
727
|
+
const headers = {
|
|
728
|
+
"Content-Type": contentType,
|
|
729
|
+
"Content-Length": String(untagged.length)
|
|
730
|
+
};
|
|
731
|
+
return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
|
|
732
|
+
headers
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
723
736
|
const totalSize = stat.size;
|
|
724
737
|
const rangeHeader = c.req.header("range");
|
|
725
738
|
const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
|
|
@@ -747,7 +760,8 @@ function createFileServer(options) {
|
|
|
747
760
|
const stream = createReadStream(filePath, {
|
|
748
761
|
fd,
|
|
749
762
|
autoClose: true,
|
|
750
|
-
|
|
763
|
+
start: range?.start ?? 0,
|
|
764
|
+
...range ? { end: range.end } : {}
|
|
751
765
|
});
|
|
752
766
|
streamOwnsFd = true;
|
|
753
767
|
try {
|
|
@@ -761,10 +775,10 @@ function createFileServer(options) {
|
|
|
761
775
|
if (!streamOwnsFd) closeSync(fd);
|
|
762
776
|
}
|
|
763
777
|
});
|
|
764
|
-
return new Promise((
|
|
778
|
+
return new Promise((resolve13) => {
|
|
765
779
|
const connections = /* @__PURE__ */ new Set();
|
|
766
780
|
const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
|
|
767
|
-
|
|
781
|
+
resolve13({
|
|
768
782
|
url: `http://localhost:${info.port}`,
|
|
769
783
|
port: info.port,
|
|
770
784
|
addPreHeadScript: (script) => {
|
|
@@ -1271,8 +1285,8 @@ var OrderedRenderEventPublisher = class {
|
|
|
1271
1285
|
let timer;
|
|
1272
1286
|
const drained = await Promise.race([
|
|
1273
1287
|
this.tail.then(() => true),
|
|
1274
|
-
new Promise((
|
|
1275
|
-
timer = setTimeout(() =>
|
|
1288
|
+
new Promise((resolve13) => {
|
|
1289
|
+
timer = setTimeout(() => resolve13(false), FLUSH_TIMEOUT_MS);
|
|
1276
1290
|
})
|
|
1277
1291
|
]);
|
|
1278
1292
|
clearTimeout(timer);
|
|
@@ -3616,7 +3630,7 @@ var SequentialCaptureStallError = class extends Error {
|
|
|
3616
3630
|
}
|
|
3617
3631
|
};
|
|
3618
3632
|
function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
3619
|
-
return new Promise((
|
|
3633
|
+
return new Promise((resolve13, reject) => {
|
|
3620
3634
|
const timer = setTimeout(
|
|
3621
3635
|
() => {
|
|
3622
3636
|
reject(
|
|
@@ -3635,7 +3649,7 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
|
3635
3649
|
promise.then(
|
|
3636
3650
|
(value) => {
|
|
3637
3651
|
clearTimeout(timer);
|
|
3638
|
-
|
|
3652
|
+
resolve13(value);
|
|
3639
3653
|
},
|
|
3640
3654
|
(error) => {
|
|
3641
3655
|
clearTimeout(timer);
|
|
@@ -5563,15 +5577,15 @@ function countAuthoredTimedClips(html) {
|
|
|
5563
5577
|
}
|
|
5564
5578
|
|
|
5565
5579
|
// src/services/render/stages/compileStage.ts
|
|
5566
|
-
import { join as
|
|
5580
|
+
import { join as join16 } from "node:path";
|
|
5567
5581
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5568
5582
|
|
|
5569
5583
|
// src/services/htmlCompiler.ts
|
|
5570
5584
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5571
|
-
import { createHash as
|
|
5572
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as
|
|
5573
|
-
import { join as
|
|
5574
|
-
import { parseHTML as
|
|
5585
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
5586
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5587
|
+
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5588
|
+
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5575
5589
|
import {
|
|
5576
5590
|
COMPOSITION_SOURCE_URL,
|
|
5577
5591
|
compileTimingAttrs,
|
|
@@ -5847,7 +5861,7 @@ function readLoopOverride(el) {
|
|
|
5847
5861
|
}
|
|
5848
5862
|
return null;
|
|
5849
5863
|
}
|
|
5850
|
-
function
|
|
5864
|
+
function resolveLocalImageSourcePath(src, options) {
|
|
5851
5865
|
const trimmed = src.trim();
|
|
5852
5866
|
if (!trimmed || trimmed.startsWith("data:")) return null;
|
|
5853
5867
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
@@ -6070,7 +6084,7 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6070
6084
|
const img = images[i];
|
|
6071
6085
|
const src = (img.getAttribute("src") || "").trim();
|
|
6072
6086
|
if (!hasGifExtension(src)) continue;
|
|
6073
|
-
const sourcePath =
|
|
6087
|
+
const sourcePath = resolveLocalImageSourcePath(src, options);
|
|
6074
6088
|
if (!sourcePath) continue;
|
|
6075
6089
|
const bytes = readFileSync5(sourcePath);
|
|
6076
6090
|
const metadata = parseAnimatedGifMetadata(bytes);
|
|
@@ -6118,19 +6132,74 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
6118
6132
|
};
|
|
6119
6133
|
}
|
|
6120
6134
|
|
|
6121
|
-
// src/services/
|
|
6122
|
-
import {
|
|
6123
|
-
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
6124
|
-
|
|
6125
|
-
// src/services/assetMediaType.ts
|
|
6126
|
-
import { existsSync as existsSync9 } from "node:fs";
|
|
6135
|
+
// src/services/heifImagePrep.ts
|
|
6136
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6127
6137
|
import {
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6138
|
+
closeSync as closeSync4,
|
|
6139
|
+
copyFileSync as copyFileSync3,
|
|
6140
|
+
mkdirSync as mkdirSync6,
|
|
6141
|
+
openSync as openSync4,
|
|
6142
|
+
readFileSync as readFileSync6,
|
|
6143
|
+
readSync as readSync3,
|
|
6144
|
+
rmSync as rmSync8,
|
|
6145
|
+
statSync as statSync6,
|
|
6146
|
+
writeFileSync as writeFileSync4
|
|
6147
|
+
} from "node:fs";
|
|
6148
|
+
import { dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
|
|
6149
|
+
import { parseHTML as parseHTML4 } from "linkedom";
|
|
6150
|
+
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
6151
|
+
|
|
6152
|
+
// src/services/heifRotation.ts
|
|
6153
|
+
function* boxes(buf, start, end) {
|
|
6154
|
+
let at = start;
|
|
6155
|
+
while (at + 8 <= end) {
|
|
6156
|
+
let size = buf.readUInt32BE(at);
|
|
6157
|
+
let header = 8;
|
|
6158
|
+
if (size === 1 && at + 16 <= end) {
|
|
6159
|
+
size = Number(buf.readBigUInt64BE(at + 8));
|
|
6160
|
+
header = 16;
|
|
6161
|
+
} else if (size === 0) {
|
|
6162
|
+
size = end - at;
|
|
6163
|
+
}
|
|
6164
|
+
if (size < header || at + size > end) return;
|
|
6165
|
+
yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
|
|
6166
|
+
at += size;
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
function child(buf, parent, type, skip = 0) {
|
|
6170
|
+
return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
|
|
6171
|
+
}
|
|
6172
|
+
function readHeifPrimaryRotation(buf) {
|
|
6173
|
+
try {
|
|
6174
|
+
const root = { type: "", start: 0, end: buf.length };
|
|
6175
|
+
const meta = child(buf, root, "meta")[0];
|
|
6176
|
+
const pitm = meta && child(buf, meta, "pitm", 4)[0];
|
|
6177
|
+
const iprp = meta && child(buf, meta, "iprp", 4)[0];
|
|
6178
|
+
const ipco = iprp && child(buf, iprp, "ipco")[0];
|
|
6179
|
+
if (!pitm || !ipco || !iprp) return 0;
|
|
6180
|
+
const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
|
|
6181
|
+
const props = [...boxes(buf, ipco.start, ipco.end)];
|
|
6182
|
+
for (const ipma of child(buf, iprp, "ipma")) {
|
|
6183
|
+
const version = buf[ipma.start];
|
|
6184
|
+
const wide = (buf[ipma.start + 3] & 1) === 1;
|
|
6185
|
+
let at = ipma.start + 8;
|
|
6186
|
+
for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
|
|
6187
|
+
const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
|
|
6188
|
+
at += version < 1 ? 2 : 4;
|
|
6189
|
+
const count = buf[at++];
|
|
6190
|
+
for (let i = 0; i < count; i++) {
|
|
6191
|
+
const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
|
|
6192
|
+
at += wide ? 2 : 1;
|
|
6193
|
+
const prop = props[index - 1];
|
|
6194
|
+
if (id === primaryId && prop?.type === "irot")
|
|
6195
|
+
return (buf[prop.start] & 3) * 90;
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
}
|
|
6199
|
+
} catch {
|
|
6200
|
+
}
|
|
6201
|
+
return 0;
|
|
6202
|
+
}
|
|
6134
6203
|
|
|
6135
6204
|
// src/utils/semaphore.ts
|
|
6136
6205
|
var Semaphore = class {
|
|
@@ -6145,10 +6214,10 @@ var Semaphore = class {
|
|
|
6145
6214
|
this.active++;
|
|
6146
6215
|
return () => this.release();
|
|
6147
6216
|
}
|
|
6148
|
-
return new Promise((
|
|
6217
|
+
return new Promise((resolve13) => {
|
|
6149
6218
|
this.queue.push(() => {
|
|
6150
6219
|
this.active++;
|
|
6151
|
-
|
|
6220
|
+
resolve13(() => this.release());
|
|
6152
6221
|
});
|
|
6153
6222
|
});
|
|
6154
6223
|
}
|
|
@@ -6179,7 +6248,229 @@ async function withMediaProbeSlot(operation) {
|
|
|
6179
6248
|
}
|
|
6180
6249
|
}
|
|
6181
6250
|
|
|
6251
|
+
// src/services/heifImagePrep.ts
|
|
6252
|
+
var PREPARED_HEIF_SUBDIR = "_heif_images";
|
|
6253
|
+
var CACHE_SCHEMA2 = "hfheif-v1";
|
|
6254
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
6255
|
+
var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
|
|
6256
|
+
var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
|
|
6257
|
+
var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
|
|
6258
|
+
var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
6259
|
+
var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
|
|
6260
|
+
var HeifImageConversionError = class extends Error {
|
|
6261
|
+
code = HEIF_IMAGE_CONVERSION_FAILED;
|
|
6262
|
+
owner = "user";
|
|
6263
|
+
retryable = false;
|
|
6264
|
+
constructor(label, reason) {
|
|
6265
|
+
super(
|
|
6266
|
+
`Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
|
|
6267
|
+
);
|
|
6268
|
+
this.name = "HeifImageConversionError";
|
|
6269
|
+
}
|
|
6270
|
+
};
|
|
6271
|
+
var HeifToolUnavailableError = class extends Error {
|
|
6272
|
+
code = HEIF_TOOL_UNAVAILABLE;
|
|
6273
|
+
owner = "system";
|
|
6274
|
+
retryable = false;
|
|
6275
|
+
constructor(label, reason) {
|
|
6276
|
+
super(
|
|
6277
|
+
`Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
|
|
6278
|
+
);
|
|
6279
|
+
this.name = "HeifToolUnavailableError";
|
|
6280
|
+
}
|
|
6281
|
+
};
|
|
6282
|
+
var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
|
|
6283
|
+
function isHeifBytes(head) {
|
|
6284
|
+
const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
|
|
6285
|
+
if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
|
|
6286
|
+
const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
|
|
6287
|
+
if (boxEnd < 16) return false;
|
|
6288
|
+
const brands = [bytes.toString("ascii", 8, 12)];
|
|
6289
|
+
for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
|
|
6290
|
+
brands.push(bytes.toString("ascii", offset, offset + 4));
|
|
6291
|
+
}
|
|
6292
|
+
return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
|
|
6293
|
+
}
|
|
6294
|
+
function readHead(path) {
|
|
6295
|
+
let fd;
|
|
6296
|
+
try {
|
|
6297
|
+
fd = openSync4(path, "r");
|
|
6298
|
+
const buf = Buffer.alloc(4096);
|
|
6299
|
+
return buf.subarray(0, readSync3(fd, buf, 0, buf.length, 0));
|
|
6300
|
+
} catch {
|
|
6301
|
+
return null;
|
|
6302
|
+
} finally {
|
|
6303
|
+
if (fd !== void 0) closeSync4(fd);
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
async function probeJson(path, withGroups) {
|
|
6307
|
+
const stdout = await runFfprobe(
|
|
6308
|
+
path,
|
|
6309
|
+
withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
|
|
6310
|
+
);
|
|
6311
|
+
return JSON.parse(stdout);
|
|
6312
|
+
}
|
|
6313
|
+
var ROTATION_FILTER = {
|
|
6314
|
+
0: "",
|
|
6315
|
+
90: "transpose=2",
|
|
6316
|
+
180: "hflip,vflip",
|
|
6317
|
+
270: "transpose=1"
|
|
6318
|
+
};
|
|
6319
|
+
function primaryGrid(probed) {
|
|
6320
|
+
const groups = probed.stream_groups ?? [];
|
|
6321
|
+
return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
|
|
6322
|
+
}
|
|
6323
|
+
async function convertHeifToJpeg(request) {
|
|
6324
|
+
const probed = await probeJson(request.inputPath, true);
|
|
6325
|
+
const grid = primaryGrid(probed);
|
|
6326
|
+
const rotation = readHeifPrimaryRotation(readFileSync6(request.inputPath));
|
|
6327
|
+
const filters = [];
|
|
6328
|
+
let inputs = [];
|
|
6329
|
+
let size = probed.streams?.[0];
|
|
6330
|
+
if (grid?.subcomponents?.length) {
|
|
6331
|
+
size = grid;
|
|
6332
|
+
const layout = grid.subcomponents.map(
|
|
6333
|
+
(t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
|
|
6334
|
+
);
|
|
6335
|
+
inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
|
|
6336
|
+
filters.push(
|
|
6337
|
+
`${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
|
|
6338
|
+
`crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
|
|
6339
|
+
);
|
|
6340
|
+
}
|
|
6341
|
+
if (!size?.width || !size.height) throw new Error("no declared image size");
|
|
6342
|
+
if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
|
|
6343
|
+
const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
|
|
6344
|
+
const result = await runFfmpeg3(
|
|
6345
|
+
[
|
|
6346
|
+
"-hide_banner",
|
|
6347
|
+
"-loglevel",
|
|
6348
|
+
"error",
|
|
6349
|
+
"-i",
|
|
6350
|
+
request.inputPath,
|
|
6351
|
+
...mapArgs,
|
|
6352
|
+
"-frames:v",
|
|
6353
|
+
"1",
|
|
6354
|
+
"-q:v",
|
|
6355
|
+
"2",
|
|
6356
|
+
"-f",
|
|
6357
|
+
"mjpeg",
|
|
6358
|
+
"-y",
|
|
6359
|
+
request.outputPath
|
|
6360
|
+
],
|
|
6361
|
+
{ timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
|
|
6362
|
+
);
|
|
6363
|
+
if (!result.success) {
|
|
6364
|
+
throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
|
|
6365
|
+
}
|
|
6366
|
+
const out = (await probeJson(request.outputPath, false)).streams?.[0];
|
|
6367
|
+
const same = out?.width === size.width && out.height === size.height;
|
|
6368
|
+
const swapped = out?.width === size.height && out.height === size.width;
|
|
6369
|
+
if (!same && !swapped) {
|
|
6370
|
+
throw new Error(
|
|
6371
|
+
`wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
function resolveImageSource(src, options) {
|
|
6376
|
+
const data = DATA_URL_RE.exec(src);
|
|
6377
|
+
if (data) {
|
|
6378
|
+
const bytes = Buffer.from(data[1], "base64");
|
|
6379
|
+
return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
|
|
6380
|
+
}
|
|
6381
|
+
const path = resolveLocalImageSourcePath(src, options);
|
|
6382
|
+
const head = path ? readHead(path) : null;
|
|
6383
|
+
if (!path || !head || !isHeifBytes(head)) return null;
|
|
6384
|
+
return { path, bytes: readFileSync6(path), label: `"${splitUrlSuffix(src).basePath}"` };
|
|
6385
|
+
}
|
|
6386
|
+
function isUsableFile2(path) {
|
|
6387
|
+
try {
|
|
6388
|
+
return statSync6(path).size > 0;
|
|
6389
|
+
} catch {
|
|
6390
|
+
return false;
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
6394
|
+
mkdirSync6(dirname6(cachePath), { recursive: true });
|
|
6395
|
+
mkdirSync6(dirname6(outputPath), { recursive: true });
|
|
6396
|
+
if (!isUsableFile2(cachePath)) {
|
|
6397
|
+
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
6398
|
+
const inlinePath = `${tmpPath}.heic`;
|
|
6399
|
+
const inputPath = source.path ?? inlinePath;
|
|
6400
|
+
try {
|
|
6401
|
+
if (!source.path) writeFileSync4(inlinePath, source.bytes);
|
|
6402
|
+
await (options.transcode ?? convertHeifToJpeg)({
|
|
6403
|
+
inputPath,
|
|
6404
|
+
outputPath: tmpPath,
|
|
6405
|
+
timeoutMs: options.timeoutMs
|
|
6406
|
+
});
|
|
6407
|
+
if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
|
|
6408
|
+
renameWithRetry4(tmpPath, cachePath);
|
|
6409
|
+
} catch (error) {
|
|
6410
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6411
|
+
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
6412
|
+
} finally {
|
|
6413
|
+
rmSync8(tmpPath, { force: true });
|
|
6414
|
+
rmSync8(inlinePath, { force: true });
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
|
|
6418
|
+
copyFileSync3(cachePath, outputPath);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
async function prepareHeifImageInputs(html, options) {
|
|
6422
|
+
const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
6423
|
+
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
6424
|
+
const cacheDir = options.cacheDir ?? outputDir;
|
|
6425
|
+
const preparedAssets = /* @__PURE__ */ new Map();
|
|
6426
|
+
const preparedImages = [];
|
|
6427
|
+
if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
|
|
6428
|
+
const { document: document2 } = parseHTML4(html);
|
|
6429
|
+
const found = [];
|
|
6430
|
+
for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
|
|
6431
|
+
const src = (img.getAttribute("src") || "").trim();
|
|
6432
|
+
const source = src ? resolveImageSource(src, options) : null;
|
|
6433
|
+
if (!source) continue;
|
|
6434
|
+
const hash = createHash5("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
|
|
6435
|
+
found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
|
|
6436
|
+
}
|
|
6437
|
+
if (found.length === 0) return { html, preparedAssets, preparedImages };
|
|
6438
|
+
const unique = new Map(found.map((item) => [item.filename, item.source]));
|
|
6439
|
+
const failed = /* @__PURE__ */ new Set();
|
|
6440
|
+
await Promise.all(
|
|
6441
|
+
[...unique].map(
|
|
6442
|
+
([filename, source]) => withMediaProbeSlot(
|
|
6443
|
+
() => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
|
|
6444
|
+
).catch((error) => {
|
|
6445
|
+
if (!options.onFailure) throw error;
|
|
6446
|
+
failed.add(filename);
|
|
6447
|
+
options.onFailure(error);
|
|
6448
|
+
})
|
|
6449
|
+
)
|
|
6450
|
+
);
|
|
6451
|
+
for (const { img, src, filename } of found) {
|
|
6452
|
+
if (failed.has(filename)) continue;
|
|
6453
|
+
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
6454
|
+
img.setAttribute("src", outputSrc);
|
|
6455
|
+
preparedAssets.set(outputSrc, join14(outputDir, filename));
|
|
6456
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join14(outputDir, filename) });
|
|
6457
|
+
}
|
|
6458
|
+
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
// src/services/htmlCompiler.ts
|
|
6462
|
+
import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
|
|
6463
|
+
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
6464
|
+
|
|
6182
6465
|
// src/services/assetMediaType.ts
|
|
6466
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
6467
|
+
import {
|
|
6468
|
+
fingerprintElementId,
|
|
6469
|
+
isNotMediaPayload,
|
|
6470
|
+
NotMediaPayloadError,
|
|
6471
|
+
probeMediaProfile,
|
|
6472
|
+
resolveProjectRelativeSrc as resolveProjectRelativeSrc2
|
|
6473
|
+
} from "@hyperframes/engine";
|
|
6183
6474
|
var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
|
|
6184
6475
|
var AssetMediaTypeMismatchError = class extends Error {
|
|
6185
6476
|
code = ASSET_MEDIA_TYPE_MISMATCH;
|
|
@@ -6289,7 +6580,7 @@ function logRemoteDownloadTelemetry(event) {
|
|
|
6289
6580
|
}
|
|
6290
6581
|
var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
|
|
6291
6582
|
function parseSubCompHtmlForValidity(html) {
|
|
6292
|
-
return
|
|
6583
|
+
return parseHTML5(html).document;
|
|
6293
6584
|
}
|
|
6294
6585
|
function endsWithSourceUrl(code) {
|
|
6295
6586
|
const trimmed = code.trimEnd();
|
|
@@ -6297,7 +6588,7 @@ function endsWithSourceUrl(code) {
|
|
|
6297
6588
|
return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
|
|
6298
6589
|
}
|
|
6299
6590
|
function prepareCompositionScripts(html) {
|
|
6300
|
-
const { document: document2 } =
|
|
6591
|
+
const { document: document2 } = parseHTML5(html);
|
|
6301
6592
|
for (const el of document2.querySelectorAll("script:not([src])")) {
|
|
6302
6593
|
const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
6303
6594
|
const runsAsScript = isModule || isJavaScriptType(el);
|
|
@@ -6334,14 +6625,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
6334
6625
|
}
|
|
6335
6626
|
};
|
|
6336
6627
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
6337
|
-
const { document: document2 } =
|
|
6628
|
+
const { document: document2 } = parseHTML5(html);
|
|
6338
6629
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
6339
6630
|
const problems = [];
|
|
6340
6631
|
for (const el of hosts) {
|
|
6341
6632
|
const srcPath = el.getAttribute("data-composition-src");
|
|
6342
6633
|
if (!srcPath) continue;
|
|
6343
6634
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
6344
|
-
const filePath =
|
|
6635
|
+
const filePath = resolve7(projectDir, srcPath);
|
|
6345
6636
|
if (visited.has(filePath)) continue;
|
|
6346
6637
|
const read = readProjectFile(filePath);
|
|
6347
6638
|
if (read.kind === "missing") {
|
|
@@ -6397,7 +6688,7 @@ function runtimeRoot(document2) {
|
|
|
6397
6688
|
}
|
|
6398
6689
|
function detectRenderModeHints(html, pageMotionBlur) {
|
|
6399
6690
|
const reasons = [];
|
|
6400
|
-
const { document: document2 } =
|
|
6691
|
+
const { document: document2 } = parseHTML5(html);
|
|
6401
6692
|
if (document2.querySelector("canvas[layoutsubtree]")) {
|
|
6402
6693
|
reasons.push({
|
|
6403
6694
|
code: "htmlInCanvas",
|
|
@@ -6439,7 +6730,7 @@ function detectThreeDTransformUsage(html) {
|
|
|
6439
6730
|
}
|
|
6440
6731
|
var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
|
|
6441
6732
|
function detectAncestorBackgroundImage(html) {
|
|
6442
|
-
const { document: document2 } =
|
|
6733
|
+
const { document: document2 } = parseHTML5(html);
|
|
6443
6734
|
const root = document2.querySelector("[data-composition-id]");
|
|
6444
6735
|
if (!root) return false;
|
|
6445
6736
|
const ancestors = [];
|
|
@@ -6486,7 +6777,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
6486
6777
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
6487
6778
|
let filePath = src;
|
|
6488
6779
|
if (isHttpUrl(src)) {
|
|
6489
|
-
if (!existsSync10(downloadDir))
|
|
6780
|
+
if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
|
|
6490
6781
|
try {
|
|
6491
6782
|
filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
|
|
6492
6783
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -6496,7 +6787,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6496
6787
|
}
|
|
6497
6788
|
} else {
|
|
6498
6789
|
filePath = decodedUrlPath(src);
|
|
6499
|
-
if (!filePath.startsWith("/")) filePath =
|
|
6790
|
+
if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
|
|
6500
6791
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
6501
6792
|
}
|
|
6502
6793
|
if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
|
|
@@ -6553,7 +6844,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6553
6844
|
function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
6554
6845
|
const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
|
|
6555
6846
|
if (unresolvedIds.size === 0) return html;
|
|
6556
|
-
const { document: document2 } =
|
|
6847
|
+
const { document: document2 } = parseHTML5(html);
|
|
6557
6848
|
let changed = false;
|
|
6558
6849
|
for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
|
|
6559
6850
|
if (!unresolvedIds.has(element.id)) continue;
|
|
@@ -6624,13 +6915,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
6624
6915
|
}
|
|
6625
6916
|
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
6626
6917
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
6627
|
-
const { document: document2 } =
|
|
6918
|
+
const { document: document2 } = parseHTML5(html);
|
|
6628
6919
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
6629
6920
|
const workItems = [];
|
|
6630
6921
|
for (const el of compEls) {
|
|
6631
6922
|
const srcPath = el.getAttribute("data-composition-src");
|
|
6632
6923
|
if (!srcPath) continue;
|
|
6633
|
-
const filePath =
|
|
6924
|
+
const filePath = resolve7(projectDir, srcPath);
|
|
6634
6925
|
if (visited.has(filePath)) {
|
|
6635
6926
|
continue;
|
|
6636
6927
|
}
|
|
@@ -6647,7 +6938,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6647
6938
|
workItems.map(async (item) => {
|
|
6648
6939
|
const { html: compiledSub } = await compileHtmlFile(
|
|
6649
6940
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
6650
|
-
|
|
6941
|
+
dirname7(item.filePath),
|
|
6651
6942
|
downloadDir,
|
|
6652
6943
|
void 0,
|
|
6653
6944
|
urlByAbsentLocalPath
|
|
@@ -6675,7 +6966,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6675
6966
|
return { subCompositions };
|
|
6676
6967
|
}
|
|
6677
6968
|
function promoteCssImportsToLinkTags(html) {
|
|
6678
|
-
const { document: document2 } =
|
|
6969
|
+
const { document: document2 } = parseHTML5(html);
|
|
6679
6970
|
const head = document2.querySelector("head");
|
|
6680
6971
|
if (!head) return html;
|
|
6681
6972
|
const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
|
|
@@ -6744,7 +7035,7 @@ var ProducerHostIdentityMap = class extends Map {
|
|
|
6744
7035
|
}
|
|
6745
7036
|
};
|
|
6746
7037
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
6747
|
-
const { document: document2 } =
|
|
7038
|
+
const { document: document2 } = parseHTML5(html);
|
|
6748
7039
|
const head = document2.querySelector("head");
|
|
6749
7040
|
const body = document2.querySelector("body");
|
|
6750
7041
|
if (!head && !body) return html;
|
|
@@ -6795,7 +7086,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
|
|
|
6795
7086
|
return document2.toString();
|
|
6796
7087
|
}
|
|
6797
7088
|
function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
|
|
6798
|
-
const { document: document2 } =
|
|
7089
|
+
const { document: document2 } = parseHTML5(html);
|
|
6799
7090
|
const head = document2.querySelector("head");
|
|
6800
7091
|
const body = document2.querySelector("body");
|
|
6801
7092
|
const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
|
|
@@ -6824,17 +7115,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
6824
7115
|
resolveHtml: (srcPath) => {
|
|
6825
7116
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
6826
7117
|
if (!compHtml) {
|
|
6827
|
-
const read = readProjectFile(
|
|
7118
|
+
const read = readProjectFile(resolve7(projectDir, srcPath));
|
|
6828
7119
|
if (read.kind === "file") {
|
|
6829
7120
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
6830
7121
|
}
|
|
6831
7122
|
}
|
|
6832
7123
|
return compHtml;
|
|
6833
7124
|
},
|
|
6834
|
-
parseHtml: (htmlStr) =>
|
|
7125
|
+
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
6835
7126
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
6836
7127
|
// against its own directory, project-root refs stay as authored.
|
|
6837
|
-
assetExists: (path) => existsSync10(
|
|
7128
|
+
assetExists: (path) => existsSync10(resolve7(projectDir, path)),
|
|
6838
7129
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
6839
7130
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
6840
7131
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -6935,7 +7226,7 @@ ${html}
|
|
|
6935
7226
|
</html>`;
|
|
6936
7227
|
}
|
|
6937
7228
|
function injectTextRenderingRule(html) {
|
|
6938
|
-
const { document: document2 } =
|
|
7229
|
+
const { document: document2 } = parseHTML5(html);
|
|
6939
7230
|
const head = document2.querySelector("head");
|
|
6940
7231
|
if (!head) return html;
|
|
6941
7232
|
if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
|
|
@@ -6959,7 +7250,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
6959
7250
|
(alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
|
|
6960
7251
|
);
|
|
6961
7252
|
if (!strongest) return true;
|
|
6962
|
-
const actual =
|
|
7253
|
+
const actual = createHash6(strongest).update(bytes).digest();
|
|
6963
7254
|
return hashes.some(
|
|
6964
7255
|
(hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
|
|
6965
7256
|
);
|
|
@@ -6967,7 +7258,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
6967
7258
|
async function inlineExternalScripts(html) {
|
|
6968
7259
|
const fullHtml = ensureFullDocument(html);
|
|
6969
7260
|
const wrappedFragment = fullHtml !== html;
|
|
6970
|
-
const { document: document2 } =
|
|
7261
|
+
const { document: document2 } = parseHTML5(fullHtml);
|
|
6971
7262
|
const scripts = document2.querySelectorAll("script[src]");
|
|
6972
7263
|
const externalScripts = [];
|
|
6973
7264
|
for (const el of scripts) {
|
|
@@ -7016,12 +7307,12 @@ ${safeText}
|
|
|
7016
7307
|
return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
7017
7308
|
}
|
|
7018
7309
|
function collectExternalAssets(html, projectDir) {
|
|
7019
|
-
const absProjectDir =
|
|
7310
|
+
const absProjectDir = resolve7(projectDir);
|
|
7020
7311
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
7021
7312
|
function processPath(rawPath) {
|
|
7022
7313
|
const trimmed = rawPath.trim();
|
|
7023
7314
|
if (isNonRelativeUrl(trimmed)) return null;
|
|
7024
|
-
const absPath =
|
|
7315
|
+
const absPath = resolve7(absProjectDir, trimmed);
|
|
7025
7316
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
7026
7317
|
return null;
|
|
7027
7318
|
}
|
|
@@ -7030,7 +7321,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
7030
7321
|
externalAssets.set(safeKey, absPath);
|
|
7031
7322
|
return safeKey;
|
|
7032
7323
|
}
|
|
7033
|
-
const { document: document2 } =
|
|
7324
|
+
const { document: document2 } = parseHTML5(html);
|
|
7034
7325
|
for (const el of document2.querySelectorAll("[src], [href]")) {
|
|
7035
7326
|
for (const attr of ["src", "href"]) {
|
|
7036
7327
|
const val = (el.getAttribute(attr) || "").trim();
|
|
@@ -7075,7 +7366,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
7075
7366
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
7076
7367
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
7077
7368
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
7078
|
-
if (!existsSync10(remoteDir))
|
|
7369
|
+
if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
|
|
7079
7370
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
7080
7371
|
await Promise.all(
|
|
7081
7372
|
[...urlSet].map(async (url) => {
|
|
@@ -7122,7 +7413,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
7122
7413
|
return downloadAndRewriteUrls(
|
|
7123
7414
|
urlSet,
|
|
7124
7415
|
html,
|
|
7125
|
-
|
|
7416
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7126
7417
|
"Remote media download failed",
|
|
7127
7418
|
"Localized remote media source(s)"
|
|
7128
7419
|
);
|
|
@@ -7137,7 +7428,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
7137
7428
|
return downloadAndRewriteUrls(
|
|
7138
7429
|
urlSet,
|
|
7139
7430
|
html,
|
|
7140
|
-
|
|
7431
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7141
7432
|
"Remote image download failed",
|
|
7142
7433
|
"Localized remote image source(s)"
|
|
7143
7434
|
);
|
|
@@ -7153,7 +7444,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
7153
7444
|
return downloadAndRewriteUrls(
|
|
7154
7445
|
urlSet,
|
|
7155
7446
|
html,
|
|
7156
|
-
|
|
7447
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7157
7448
|
"Remote background-image download failed",
|
|
7158
7449
|
"Localized remote background-image(s)",
|
|
7159
7450
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -7288,7 +7579,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
7288
7579
|
return downloadAndRewriteUrls(
|
|
7289
7580
|
urlSet,
|
|
7290
7581
|
processed,
|
|
7291
|
-
|
|
7582
|
+
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7292
7583
|
"Remote font download failed",
|
|
7293
7584
|
"Localized remote font face(s)",
|
|
7294
7585
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -7333,7 +7624,7 @@ async function embedLocalFontFaces(html, projectDir) {
|
|
|
7333
7624
|
while ((urlMatch = urlRe.exec(block)) !== null) {
|
|
7334
7625
|
const localPath = urlMatch[1];
|
|
7335
7626
|
if (!localPath || embeddedPaths.has(localPath)) continue;
|
|
7336
|
-
const absPath = localPath.startsWith("/") ? localPath :
|
|
7627
|
+
const absPath = localPath.startsWith("/") ? localPath : resolve7(projectDir, localPath);
|
|
7337
7628
|
if (!isPathInside2(absPath, projectDir)) continue;
|
|
7338
7629
|
const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
|
|
7339
7630
|
try {
|
|
@@ -7382,7 +7673,7 @@ function renderGsapDist() {
|
|
|
7382
7673
|
}
|
|
7383
7674
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
7384
7675
|
let gsapDist;
|
|
7385
|
-
const exists = (path) => existsSync10(
|
|
7676
|
+
const exists = (path) => existsSync10(resolve7(projectDir, path));
|
|
7386
7677
|
return html.replace(
|
|
7387
7678
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
7388
7679
|
(full, prefix, src, file, suffix) => {
|
|
@@ -7398,8 +7689,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
7398
7689
|
if (!isPathInside2(htmlPath, projectDir)) return html;
|
|
7399
7690
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
7400
7691
|
if (!entryPath.includes("/")) return html;
|
|
7401
|
-
const { document: document2 } =
|
|
7402
|
-
const assetExists = (path) => existsSync10(
|
|
7692
|
+
const { document: document2 } = parseHTML5(html);
|
|
7693
|
+
const assetExists = (path) => existsSync10(resolve7(projectDir, path));
|
|
7403
7694
|
rewriteAssetPaths(
|
|
7404
7695
|
document2.querySelectorAll("[src], [href]"),
|
|
7405
7696
|
entryPath,
|
|
@@ -7421,7 +7712,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
7421
7712
|
}
|
|
7422
7713
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
7423
7714
|
const entryHtml = rebaseDirectEntryAssetPaths(
|
|
7424
|
-
|
|
7715
|
+
readFileSync7(htmlPath, "utf-8"),
|
|
7425
7716
|
projectDir,
|
|
7426
7717
|
htmlPath
|
|
7427
7718
|
);
|
|
@@ -7491,12 +7782,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7491
7782
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
7492
7783
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
7493
7784
|
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
|
|
7785
|
+
const {
|
|
7786
|
+
html: htmlWithJpegs,
|
|
7787
|
+
preparedAssets: preparedHeifAssets,
|
|
7788
|
+
preparedImages: preparedHeifImages
|
|
7789
|
+
} = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
|
|
7790
|
+
projectDir,
|
|
7791
|
+
downloadDir,
|
|
7792
|
+
cacheDir: options.animatedGifCacheDir,
|
|
7793
|
+
sourceAssets: remoteImageAssets,
|
|
7794
|
+
timeoutMs: options.ffmpegProcessTimeout
|
|
7795
|
+
});
|
|
7796
|
+
if (preparedHeifImages.length > 0) {
|
|
7797
|
+
defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
|
|
7798
|
+
}
|
|
7494
7799
|
const gifSourceAssets = new Map(remoteImageAssets);
|
|
7495
7800
|
const {
|
|
7496
7801
|
html: htmlWithPreparedGifs,
|
|
7497
7802
|
preparedAssets: preparedGifAssets,
|
|
7498
7803
|
preparedGifs
|
|
7499
|
-
} = await prepareAnimatedGifInputs(
|
|
7804
|
+
} = await prepareAnimatedGifInputs(htmlWithJpegs, {
|
|
7500
7805
|
projectDir,
|
|
7501
7806
|
downloadDir,
|
|
7502
7807
|
cacheDir: options.animatedGifCacheDir,
|
|
@@ -7523,11 +7828,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7523
7828
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
7524
7829
|
externalAssets.set(relPath, absPath);
|
|
7525
7830
|
}
|
|
7831
|
+
for (const [relPath, absPath] of preparedHeifAssets) {
|
|
7832
|
+
externalAssets.set(relPath, absPath);
|
|
7833
|
+
}
|
|
7526
7834
|
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7527
7835
|
const masterFxChain = parseMasterFxChain(html);
|
|
7528
7836
|
for (const video of videos) {
|
|
7529
7837
|
if (isHttpUrl(video.src)) continue;
|
|
7530
|
-
const videoPath =
|
|
7838
|
+
const videoPath = resolve7(projectDir, video.src);
|
|
7531
7839
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
7532
7840
|
(async () => {
|
|
7533
7841
|
const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
|
|
@@ -7547,7 +7855,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7547
7855
|
})().catch(() => {
|
|
7548
7856
|
});
|
|
7549
7857
|
}
|
|
7550
|
-
const { document: document2 } =
|
|
7858
|
+
const { document: document2 } = parseHTML5(html);
|
|
7551
7859
|
const rootEl = document2.querySelector("[data-composition-id]");
|
|
7552
7860
|
const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
|
|
7553
7861
|
const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
|
|
@@ -7954,7 +8262,7 @@ async function runCompileStage(input) {
|
|
|
7954
8262
|
abortSignal
|
|
7955
8263
|
} = input;
|
|
7956
8264
|
const compileStart = Date.now();
|
|
7957
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
8265
|
+
const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
|
|
7958
8266
|
log,
|
|
7959
8267
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
7960
8268
|
allowSystemFontCapture,
|
|
@@ -7962,7 +8270,7 @@ async function runCompileStage(input) {
|
|
|
7962
8270
|
variables: input.variables,
|
|
7963
8271
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
7964
8272
|
pageMotionBlur: input.pageMotionBlur,
|
|
7965
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
8273
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
7966
8274
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
7967
8275
|
});
|
|
7968
8276
|
assertNotAborted();
|
|
@@ -8018,7 +8326,7 @@ async function runCompileStage(input) {
|
|
|
8018
8326
|
};
|
|
8019
8327
|
await preflightCompositionAssetMediaTypes({
|
|
8020
8328
|
projectDir,
|
|
8021
|
-
compiledDir:
|
|
8329
|
+
compiledDir: join16(workDir, "compiled"),
|
|
8022
8330
|
composition,
|
|
8023
8331
|
signal: abortSignal
|
|
8024
8332
|
});
|
|
@@ -8063,8 +8371,8 @@ async function runCompileStage(input) {
|
|
|
8063
8371
|
}
|
|
8064
8372
|
|
|
8065
8373
|
// src/services/render/stages/probeStage.ts
|
|
8066
|
-
import { join as
|
|
8067
|
-
import { parseHTML as
|
|
8374
|
+
import { join as join17 } from "node:path";
|
|
8375
|
+
import { parseHTML as parseHTML6 } from "linkedom";
|
|
8068
8376
|
import {
|
|
8069
8377
|
closeCaptureSession as closeCaptureSession4,
|
|
8070
8378
|
compositionRequiresWebGpu,
|
|
@@ -8111,7 +8419,7 @@ function durationToFrameCount(duration, fps) {
|
|
|
8111
8419
|
}
|
|
8112
8420
|
function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
8113
8421
|
if (audioCount <= 0) return false;
|
|
8114
|
-
const { document: document2 } =
|
|
8422
|
+
const { document: document2 } = parseHTML6(html);
|
|
8115
8423
|
const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
|
|
8116
8424
|
if (!scriptBodies) return false;
|
|
8117
8425
|
return /\.\s*volume\s*=/i.test(scriptBodies) || /\b(?:gsap|tl|timeline|tween)\s*\.\s*(?:to|fromTo|set)\s*\([\s\S]{0,2000}\bvolume\s*:/i.test(
|
|
@@ -8119,12 +8427,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
|
8119
8427
|
);
|
|
8120
8428
|
}
|
|
8121
8429
|
function hasAutoStartVideos(html) {
|
|
8122
|
-
const { document: document2 } =
|
|
8430
|
+
const { document: document2 } = parseHTML6(html);
|
|
8123
8431
|
return document2.querySelector("video[data-hf-auto-start]") !== null;
|
|
8124
8432
|
}
|
|
8125
8433
|
function hasVariableBoundMedia(html, variables) {
|
|
8126
8434
|
if (!variables || Object.keys(variables).length === 0) return false;
|
|
8127
|
-
const { document: document2 } =
|
|
8435
|
+
const { document: document2 } = parseHTML6(html);
|
|
8128
8436
|
return Array.from(
|
|
8129
8437
|
document2.querySelectorAll(
|
|
8130
8438
|
"img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
|
|
@@ -8135,7 +8443,7 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
8135
8443
|
});
|
|
8136
8444
|
}
|
|
8137
8445
|
function authoredMediaStarts(html) {
|
|
8138
|
-
const { document: document2 } =
|
|
8446
|
+
const { document: document2 } = parseHTML6(html);
|
|
8139
8447
|
const starts = /* @__PURE__ */ new Map();
|
|
8140
8448
|
for (const media of document2.querySelectorAll("video, audio")) {
|
|
8141
8449
|
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
@@ -8172,7 +8480,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
|
|
|
8172
8480
|
return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
|
|
8173
8481
|
}
|
|
8174
8482
|
function hasRuntimeMediaChanges(html) {
|
|
8175
|
-
const { document: document2 } =
|
|
8483
|
+
const { document: document2 } = parseHTML6(html);
|
|
8176
8484
|
const scriptBodies = [...document2.querySelectorAll("script")].map(
|
|
8177
8485
|
(script) => script.textContent ?? ""
|
|
8178
8486
|
);
|
|
@@ -8255,7 +8563,7 @@ async function runProbeStage(input) {
|
|
|
8255
8563
|
});
|
|
8256
8564
|
fileServer = await createFileServer({
|
|
8257
8565
|
projectDir,
|
|
8258
|
-
compiledDir:
|
|
8566
|
+
compiledDir: join17(workDir, "compiled"),
|
|
8259
8567
|
port: 0,
|
|
8260
8568
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
8261
8569
|
fps: job.config.fps
|
|
@@ -8284,7 +8592,7 @@ async function runProbeStage(input) {
|
|
|
8284
8592
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
8285
8593
|
probeSession = await createCaptureSession4(
|
|
8286
8594
|
fileServer.url,
|
|
8287
|
-
|
|
8595
|
+
join17(workDir, "probe"),
|
|
8288
8596
|
captureOpts,
|
|
8289
8597
|
null,
|
|
8290
8598
|
probeCfg
|
|
@@ -8369,7 +8677,7 @@ async function runProbeStage(input) {
|
|
|
8369
8677
|
});
|
|
8370
8678
|
probeSession = await createCaptureSession4(
|
|
8371
8679
|
fileServer.url,
|
|
8372
|
-
|
|
8680
|
+
join17(workDir, "probe-screenshot"),
|
|
8373
8681
|
captureOpts,
|
|
8374
8682
|
null,
|
|
8375
8683
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -8405,7 +8713,7 @@ async function runProbeStage(input) {
|
|
|
8405
8713
|
compiled,
|
|
8406
8714
|
resolutions,
|
|
8407
8715
|
projectDir,
|
|
8408
|
-
|
|
8716
|
+
join17(workDir, "downloads")
|
|
8409
8717
|
);
|
|
8410
8718
|
assertNotAborted();
|
|
8411
8719
|
composition.videos = compiled.videos;
|
|
@@ -8596,7 +8904,7 @@ async function runProbeStage(input) {
|
|
|
8596
8904
|
}
|
|
8597
8905
|
await preflightCompositionAssetMediaTypes({
|
|
8598
8906
|
projectDir,
|
|
8599
|
-
compiledDir:
|
|
8907
|
+
compiledDir: join17(workDir, "compiled"),
|
|
8600
8908
|
composition,
|
|
8601
8909
|
signal: abortSignal
|
|
8602
8910
|
});
|
|
@@ -8761,7 +9069,7 @@ function validateDistributedDuration(input) {
|
|
|
8761
9069
|
|
|
8762
9070
|
// src/services/render/stages/extractVideosStage.ts
|
|
8763
9071
|
import { existsSync as existsSync11 } from "node:fs";
|
|
8764
|
-
import { join as
|
|
9072
|
+
import { join as join18 } from "node:path";
|
|
8765
9073
|
import {
|
|
8766
9074
|
classifyVideoExtractionError,
|
|
8767
9075
|
createFrameLookupTable,
|
|
@@ -9081,14 +9389,14 @@ async function runExtractVideosStage(input) {
|
|
|
9081
9389
|
// because short boundary counts can differ by one frame.
|
|
9082
9390
|
{
|
|
9083
9391
|
fps: job.config.fps,
|
|
9084
|
-
outputDir:
|
|
9392
|
+
outputDir: join18(compiledDir, "__hyperframes_video_frames"),
|
|
9085
9393
|
format: job.config.videoFrameFormat ?? "auto",
|
|
9086
9394
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
9087
9395
|
timelineEnd: composition.duration,
|
|
9088
9396
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
9089
9397
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
9090
9398
|
deferRangeExtraction,
|
|
9091
|
-
contentKeyedDirs: [
|
|
9399
|
+
contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
9092
9400
|
urlByAbsentLocalPath
|
|
9093
9401
|
},
|
|
9094
9402
|
abortSignal,
|
|
@@ -9155,8 +9463,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
9155
9463
|
}
|
|
9156
9464
|
|
|
9157
9465
|
// src/services/render/stages/audioStage.ts
|
|
9158
|
-
import { join as
|
|
9159
|
-
import { rmSync as
|
|
9466
|
+
import { join as join20 } from "node:path";
|
|
9467
|
+
import { rmSync as rmSync10 } from "node:fs";
|
|
9160
9468
|
import {
|
|
9161
9469
|
MIXED_AUDIO_FILENAME,
|
|
9162
9470
|
audioFailureDiagnostics,
|
|
@@ -9165,8 +9473,8 @@ import {
|
|
|
9165
9473
|
|
|
9166
9474
|
// src/services/render/audioPadTrim.ts
|
|
9167
9475
|
import { spawn } from "node:child_process";
|
|
9168
|
-
import { mkdtempSync as mkdtempSync3, rmSync as
|
|
9169
|
-
import { dirname as
|
|
9476
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync9 } from "node:fs";
|
|
9477
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9170
9478
|
import {
|
|
9171
9479
|
DEFAULT_CONFIG,
|
|
9172
9480
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -9174,8 +9482,8 @@ import {
|
|
|
9174
9482
|
extractAudioMetadata as extractAudioMetadata3,
|
|
9175
9483
|
getFfprobeBinary,
|
|
9176
9484
|
ManagedChildProcess,
|
|
9177
|
-
renameWithRetry as
|
|
9178
|
-
runFfmpeg as
|
|
9485
|
+
renameWithRetry as renameWithRetry5,
|
|
9486
|
+
runFfmpeg as runFfmpeg5,
|
|
9179
9487
|
trackChildProcess
|
|
9180
9488
|
} from "@hyperframes/engine";
|
|
9181
9489
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
@@ -9185,7 +9493,7 @@ import {
|
|
|
9185
9493
|
aacSegmentCount,
|
|
9186
9494
|
describeFfmpegFailure,
|
|
9187
9495
|
extractAudioMetadata as extractAudioMetadata2,
|
|
9188
|
-
runFfmpeg as
|
|
9496
|
+
runFfmpeg as runFfmpeg4
|
|
9189
9497
|
} from "@hyperframes/engine";
|
|
9190
9498
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
9191
9499
|
function checkedDbfs(value) {
|
|
@@ -9209,7 +9517,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
9209
9517
|
else laterBlocks = Math.max(laterBlocks, block);
|
|
9210
9518
|
}
|
|
9211
9519
|
};
|
|
9212
|
-
const result = await
|
|
9520
|
+
const result = await runFfmpeg4(
|
|
9213
9521
|
["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
|
|
9214
9522
|
{ signal, timeout: timeoutMs, onStderr: readLines }
|
|
9215
9523
|
);
|
|
@@ -9369,7 +9677,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
9369
9677
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
9370
9678
|
};
|
|
9371
9679
|
} finally {
|
|
9372
|
-
for (const path of plan2.cleanupPaths)
|
|
9680
|
+
for (const path of plan2.cleanupPaths) rmSync9(path, { force: true });
|
|
9373
9681
|
}
|
|
9374
9682
|
let loweredDb = 0;
|
|
9375
9683
|
if (probeTruePeak) {
|
|
@@ -9416,8 +9724,8 @@ async function constrainAacTruePeak(input) {
|
|
|
9416
9724
|
async function enforceAacTruePeak(input) {
|
|
9417
9725
|
let scratchDir;
|
|
9418
9726
|
try {
|
|
9419
|
-
scratchDir = mkdtempSync3(
|
|
9420
|
-
const correctedPath =
|
|
9727
|
+
scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
|
|
9728
|
+
const correctedPath = join19(scratchDir, "audio.m4a");
|
|
9421
9729
|
const measurements = [];
|
|
9422
9730
|
let attenuationDb = 0;
|
|
9423
9731
|
let sourcePeakDbfs;
|
|
@@ -9432,7 +9740,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9432
9740
|
}
|
|
9433
9741
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
9434
9742
|
if (measuredPath !== correctedPath) return { success: true };
|
|
9435
|
-
|
|
9743
|
+
renameWithRetry5(correctedPath, input.audioPath);
|
|
9436
9744
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
9437
9745
|
}
|
|
9438
9746
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -9473,7 +9781,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9473
9781
|
)}`
|
|
9474
9782
|
};
|
|
9475
9783
|
} finally {
|
|
9476
|
-
if (scratchDir)
|
|
9784
|
+
if (scratchDir) rmSync9(scratchDir, { recursive: true, force: true });
|
|
9477
9785
|
}
|
|
9478
9786
|
}
|
|
9479
9787
|
function failResult(outputPath, target, source, error) {
|
|
@@ -9550,7 +9858,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
9550
9858
|
};
|
|
9551
9859
|
}
|
|
9552
9860
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
9553
|
-
return toStepResult(await
|
|
9861
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
9554
9862
|
}
|
|
9555
9863
|
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
9556
9864
|
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
@@ -9620,7 +9928,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
9620
9928
|
}
|
|
9621
9929
|
async function runAudioStage(input) {
|
|
9622
9930
|
const stageStart = Date.now();
|
|
9623
|
-
const pcmSourcePath =
|
|
9931
|
+
const pcmSourcePath = join20(input.workDir, "audio-source.wav");
|
|
9624
9932
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
9625
9933
|
try {
|
|
9626
9934
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -9640,7 +9948,7 @@ async function runAudioStage(input) {
|
|
|
9640
9948
|
}
|
|
9641
9949
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9642
9950
|
} finally {
|
|
9643
|
-
|
|
9951
|
+
rmSync10(pcmSourcePath, { force: true });
|
|
9644
9952
|
}
|
|
9645
9953
|
}
|
|
9646
9954
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -9658,7 +9966,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9658
9966
|
assertNotAborted,
|
|
9659
9967
|
log
|
|
9660
9968
|
} = input;
|
|
9661
|
-
const audioOutputPath =
|
|
9969
|
+
const audioOutputPath = join20(workDir, MIXED_AUDIO_FILENAME);
|
|
9662
9970
|
let hasAudio = false;
|
|
9663
9971
|
let audioError;
|
|
9664
9972
|
let audioFailures;
|
|
@@ -9669,7 +9977,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9669
9977
|
audioResult = await processCompositionAudio(
|
|
9670
9978
|
audios,
|
|
9671
9979
|
projectDir,
|
|
9672
|
-
|
|
9980
|
+
join20(workDir, "audio-work"),
|
|
9673
9981
|
audioOutputPath,
|
|
9674
9982
|
duration,
|
|
9675
9983
|
abortSignal,
|
|
@@ -9738,7 +10046,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
9738
10046
|
|
|
9739
10047
|
// src/services/render/stages/captureStage.ts
|
|
9740
10048
|
import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
9741
|
-
import { join as
|
|
10049
|
+
import { join as join21 } from "node:path";
|
|
9742
10050
|
import {
|
|
9743
10051
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
9744
10052
|
frameFileExtension,
|
|
@@ -9811,13 +10119,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
9811
10119
|
if (!existsSync12(workDir)) return total;
|
|
9812
10120
|
for (const attempt of readdirSync3(workDir)) {
|
|
9813
10121
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
9814
|
-
total += sampleDirectoryBytes(
|
|
10122
|
+
total += sampleDirectoryBytes(join21(workDir, attempt));
|
|
9815
10123
|
continue;
|
|
9816
10124
|
}
|
|
9817
10125
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
9818
|
-
const attemptDir =
|
|
10126
|
+
const attemptDir = join21(workDir, attempt);
|
|
9819
10127
|
for (const name of readdirSync3(attemptDir)) {
|
|
9820
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
10128
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join21(attemptDir, name));
|
|
9821
10129
|
}
|
|
9822
10130
|
}
|
|
9823
10131
|
return total;
|
|
@@ -10110,8 +10418,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10110
10418
|
}
|
|
10111
10419
|
|
|
10112
10420
|
// src/services/render/stages/captureHdrStage.ts
|
|
10113
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
10114
|
-
import { join as
|
|
10421
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10422
|
+
import { join as join25 } from "node:path";
|
|
10115
10423
|
import {
|
|
10116
10424
|
closeCaptureSession as closeCaptureSession7,
|
|
10117
10425
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -10183,7 +10491,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
10183
10491
|
}
|
|
10184
10492
|
|
|
10185
10493
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
10186
|
-
import { join as
|
|
10494
|
+
import { join as join22 } from "node:path";
|
|
10187
10495
|
import {
|
|
10188
10496
|
TRANSITIONS,
|
|
10189
10497
|
crossfade
|
|
@@ -10308,7 +10616,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10308
10616
|
);
|
|
10309
10617
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10310
10618
|
writeFileExclusiveSync(
|
|
10311
|
-
|
|
10619
|
+
join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10312
10620
|
normalCanvas
|
|
10313
10621
|
);
|
|
10314
10622
|
}
|
|
@@ -10340,7 +10648,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
10340
10648
|
}
|
|
10341
10649
|
|
|
10342
10650
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
10343
|
-
import { join as
|
|
10651
|
+
import { join as join24 } from "node:path";
|
|
10344
10652
|
import {
|
|
10345
10653
|
TRANSITIONS as TRANSITIONS2,
|
|
10346
10654
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -10354,7 +10662,7 @@ import {
|
|
|
10354
10662
|
// src/services/shaderTransitionWorkerPool.ts
|
|
10355
10663
|
import { Worker } from "node:worker_threads";
|
|
10356
10664
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
10357
|
-
import { dirname as
|
|
10665
|
+
import { dirname as dirname9, join as join23 } from "node:path";
|
|
10358
10666
|
import { createRequire } from "node:module";
|
|
10359
10667
|
import { existsSync as existsSync13 } from "node:fs";
|
|
10360
10668
|
import { cpus as cpus2 } from "node:os";
|
|
@@ -10367,10 +10675,10 @@ function resolveWorkerEntry(explicit) {
|
|
|
10367
10675
|
const isTs = override.endsWith(".ts");
|
|
10368
10676
|
return { path: override, isTs };
|
|
10369
10677
|
}
|
|
10370
|
-
const moduleDir =
|
|
10371
|
-
const jsPath =
|
|
10678
|
+
const moduleDir = dirname9(fileURLToPath3(import.meta.url));
|
|
10679
|
+
const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
|
|
10372
10680
|
if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
|
|
10373
|
-
const tsPath =
|
|
10681
|
+
const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
|
|
10374
10682
|
return { path: tsPath, isTs: true };
|
|
10375
10683
|
}
|
|
10376
10684
|
function buildExecArgv(entryIsTs) {
|
|
@@ -10513,8 +10821,8 @@ async function createShaderTransitionWorkerPool(opts) {
|
|
|
10513
10821
|
if (terminated) {
|
|
10514
10822
|
throw new Error("shader-blend pool already terminated");
|
|
10515
10823
|
}
|
|
10516
|
-
return new Promise((
|
|
10517
|
-
const task = traceEnabled ? { req, resolve:
|
|
10824
|
+
return new Promise((resolve13, reject) => {
|
|
10825
|
+
const task = traceEnabled ? { req, resolve: resolve13, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve13, reject };
|
|
10518
10826
|
const idle = slots.find((s) => !s.busy && !s.dead);
|
|
10519
10827
|
if (idle) {
|
|
10520
10828
|
queue.unshift(task);
|
|
@@ -10738,7 +11046,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
10738
11046
|
);
|
|
10739
11047
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
10740
11048
|
writeFileExclusiveSync(
|
|
10741
|
-
|
|
11049
|
+
join24(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
10742
11050
|
canvas
|
|
10743
11051
|
);
|
|
10744
11052
|
}
|
|
@@ -10926,9 +11234,9 @@ async function runCaptureHdrStage(input) {
|
|
|
10926
11234
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
10927
11235
|
}
|
|
10928
11236
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
10929
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
11237
|
+
const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
|
|
10930
11238
|
if (debugDumpDir && !existsSync14(debugDumpDir)) {
|
|
10931
|
-
|
|
11239
|
+
mkdirSync8(debugDumpDir, { recursive: true });
|
|
10932
11240
|
}
|
|
10933
11241
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
10934
11242
|
const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
|
|
@@ -11081,24 +11389,24 @@ async function runCaptureHdrStage(input) {
|
|
|
11081
11389
|
|
|
11082
11390
|
// src/services/render/stages/encodeStage.ts
|
|
11083
11391
|
import {
|
|
11084
|
-
copyFileSync as
|
|
11392
|
+
copyFileSync as copyFileSync4,
|
|
11085
11393
|
existsSync as existsSync15,
|
|
11086
|
-
mkdirSync as
|
|
11394
|
+
mkdirSync as mkdirSync9,
|
|
11087
11395
|
readdirSync as readdirSync4,
|
|
11088
|
-
readFileSync as
|
|
11089
|
-
rmSync as
|
|
11090
|
-
statSync as
|
|
11091
|
-
writeFileSync as
|
|
11396
|
+
readFileSync as readFileSync8,
|
|
11397
|
+
rmSync as rmSync11,
|
|
11398
|
+
statSync as statSync7,
|
|
11399
|
+
writeFileSync as writeFileSync5
|
|
11092
11400
|
} from "node:fs";
|
|
11093
11401
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
11094
|
-
import { dirname as
|
|
11402
|
+
import { dirname as dirname10, join as join26 } from "node:path";
|
|
11095
11403
|
import {
|
|
11096
11404
|
encodeFramesChunkedConcat,
|
|
11097
11405
|
encodeFramesFromDir,
|
|
11098
11406
|
formatFfmpegError,
|
|
11099
11407
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
11100
11408
|
resolveConfig,
|
|
11101
|
-
runFfmpeg as
|
|
11409
|
+
runFfmpeg as runFfmpeg6,
|
|
11102
11410
|
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
11103
11411
|
} from "@hyperframes/engine";
|
|
11104
11412
|
import {
|
|
@@ -11174,17 +11482,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
|
|
|
11174
11482
|
}
|
|
11175
11483
|
async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
11176
11484
|
const { outputPath } = argsInput;
|
|
11177
|
-
const result = await
|
|
11485
|
+
const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
|
|
11178
11486
|
if (!result.success) {
|
|
11179
11487
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
11180
11488
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11181
11489
|
}
|
|
11182
|
-
const gif =
|
|
11490
|
+
const gif = readFileSync8(outputPath);
|
|
11183
11491
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
11184
11492
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
11185
11493
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
11186
11494
|
}
|
|
11187
|
-
|
|
11495
|
+
writeFileSync5(outputPath, gif);
|
|
11188
11496
|
return null;
|
|
11189
11497
|
}
|
|
11190
11498
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -11206,17 +11514,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11206
11514
|
try {
|
|
11207
11515
|
const run = { signal: input.signal, timeout: input.timeout };
|
|
11208
11516
|
for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
|
|
11209
|
-
const result = await
|
|
11517
|
+
const result = await runFfmpeg6(args, run);
|
|
11210
11518
|
if (!result.success) {
|
|
11211
11519
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
11212
11520
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
11213
11521
|
}
|
|
11214
11522
|
}
|
|
11215
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
11523
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
11216
11524
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
11217
11525
|
if (failure) return failure;
|
|
11218
11526
|
}
|
|
11219
|
-
const fileSize = existsSync15(outputPath) ?
|
|
11527
|
+
const fileSize = existsSync15(outputPath) ? statSync7(outputPath).size : 0;
|
|
11220
11528
|
return {
|
|
11221
11529
|
success: true,
|
|
11222
11530
|
outputPath,
|
|
@@ -11225,7 +11533,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11225
11533
|
fileSize
|
|
11226
11534
|
};
|
|
11227
11535
|
} finally {
|
|
11228
|
-
|
|
11536
|
+
rmSync11(input.palettePath, { force: true });
|
|
11229
11537
|
}
|
|
11230
11538
|
}
|
|
11231
11539
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -11297,10 +11605,10 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
11297
11605
|
const stream = await encoder;
|
|
11298
11606
|
for (let next = 0; next < totalFrames; next++) {
|
|
11299
11607
|
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
11300
|
-
await new Promise((
|
|
11608
|
+
await new Promise((resolve13) => wake = resolve13);
|
|
11301
11609
|
}
|
|
11302
11610
|
if (signal.aborted) return;
|
|
11303
|
-
const mergedPath =
|
|
11611
|
+
const mergedPath = join26(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
11304
11612
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
11305
11613
|
landed.delete(next);
|
|
11306
11614
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -11375,7 +11683,7 @@ async function runEncodeStage(input) {
|
|
|
11375
11683
|
const stage5Start = Date.now();
|
|
11376
11684
|
if (isPngSequence) {
|
|
11377
11685
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
11378
|
-
if (!existsSync15(outputPath))
|
|
11686
|
+
if (!existsSync15(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
11379
11687
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
11380
11688
|
if (captured.length === 0) {
|
|
11381
11689
|
throw new Error(
|
|
@@ -11383,11 +11691,11 @@ async function runEncodeStage(input) {
|
|
|
11383
11691
|
);
|
|
11384
11692
|
}
|
|
11385
11693
|
captured.forEach((name, i) => {
|
|
11386
|
-
const dst =
|
|
11387
|
-
|
|
11694
|
+
const dst = join26(outputPath, formatExportFrameName(i, "png"));
|
|
11695
|
+
copyFileSync4(join26(framesDir, name), dst);
|
|
11388
11696
|
});
|
|
11389
11697
|
if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
|
|
11390
|
-
|
|
11698
|
+
copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
|
|
11391
11699
|
log.info(
|
|
11392
11700
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
11393
11701
|
);
|
|
@@ -11407,7 +11715,7 @@ async function runEncodeStage(input) {
|
|
|
11407
11715
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
11408
11716
|
fps: job.config.fps,
|
|
11409
11717
|
loop,
|
|
11410
|
-
palettePath:
|
|
11718
|
+
palettePath: join26(dirname10(videoOnlyPath), "gif-palette.png"),
|
|
11411
11719
|
preserveAlpha: needsAlpha,
|
|
11412
11720
|
signal: abortSignal,
|
|
11413
11721
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -11617,9 +11925,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
11617
11925
|
continue;
|
|
11618
11926
|
}
|
|
11619
11927
|
for (const name of entries) {
|
|
11620
|
-
const full =
|
|
11928
|
+
const full = join27(current, name);
|
|
11621
11929
|
try {
|
|
11622
|
-
const st =
|
|
11930
|
+
const st = statSync8(full);
|
|
11623
11931
|
if (st.isDirectory()) {
|
|
11624
11932
|
stack.push(full);
|
|
11625
11933
|
} else if (st.isFile()) {
|
|
@@ -11766,8 +12074,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11766
12074
|
const ranges = [];
|
|
11767
12075
|
let rangeStart = null;
|
|
11768
12076
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11769
|
-
const framePath =
|
|
11770
|
-
const missing = !existsSync16(framePath) ||
|
|
12077
|
+
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12078
|
+
const missing = !existsSync16(framePath) || statSync8(framePath).size <= 8;
|
|
11771
12079
|
if (missing && rangeStart === null) {
|
|
11772
12080
|
rangeStart = frameIndex;
|
|
11773
12081
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -11783,7 +12091,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
11783
12091
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
11784
12092
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
11785
12093
|
const batches = [];
|
|
11786
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
12094
|
+
const outputDirFor = (batchIndex, workerId) => join27(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
11787
12095
|
if (stride > 1) {
|
|
11788
12096
|
const spans = /* @__PURE__ */ new Map();
|
|
11789
12097
|
for (const range of ranges) {
|
|
@@ -11843,8 +12151,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11843
12151
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11844
12152
|
}
|
|
11845
12153
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11846
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
11847
|
-
return
|
|
12154
|
+
if (platform2 === "win32" || platform2 === "darwin") return join27(systemTempDir, "hf-render-");
|
|
12155
|
+
return join27(dirname11(outputPath), `work-${jobId}-`);
|
|
11848
12156
|
}
|
|
11849
12157
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
11850
12158
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -11886,7 +12194,7 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
11886
12194
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
11887
12195
|
let captured = 0;
|
|
11888
12196
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
11889
|
-
const framePath =
|
|
12197
|
+
const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
11890
12198
|
if (existsSync16(framePath)) captured++;
|
|
11891
12199
|
}
|
|
11892
12200
|
return captured;
|
|
@@ -11912,7 +12220,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
11912
12220
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
11913
12221
|
});
|
|
11914
12222
|
pendingTransientRetry = false;
|
|
11915
|
-
const attemptWorkDir =
|
|
12223
|
+
const attemptWorkDir = join27(options.workDir, `capture-attempt-${attempt}`);
|
|
11916
12224
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
11917
12225
|
missingRanges,
|
|
11918
12226
|
currentWorkers,
|
|
@@ -12518,18 +12826,18 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
12518
12826
|
};
|
|
12519
12827
|
}
|
|
12520
12828
|
function resolveRenderDebugDir() {
|
|
12521
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
12522
|
-
return
|
|
12829
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname11(fileURLToPath4(import.meta.url)), "../..");
|
|
12830
|
+
return join27(producerRoot, ".debug");
|
|
12523
12831
|
}
|
|
12524
12832
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
12525
12833
|
validateHlsRenderConfig(job.config);
|
|
12526
12834
|
const debugDir = resolveRenderDebugDir();
|
|
12527
|
-
const outputDir =
|
|
12528
|
-
if (!existsSync16(outputDir))
|
|
12529
|
-
const workDir = job.config.debug ?
|
|
12835
|
+
const outputDir = dirname11(outputPath);
|
|
12836
|
+
if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
12837
|
+
const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
12530
12838
|
const pipelineStart = Date.now();
|
|
12531
12839
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
12532
|
-
const logPath = job.config.debug ?
|
|
12840
|
+
const logPath = job.config.debug ? join27(workDir, "render.log") : null;
|
|
12533
12841
|
const execution = new RenderExecutionContext({
|
|
12534
12842
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
12535
12843
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -12543,7 +12851,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
12543
12851
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
12544
12852
|
return;
|
|
12545
12853
|
}
|
|
12546
|
-
|
|
12854
|
+
rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
12547
12855
|
});
|
|
12548
12856
|
try {
|
|
12549
12857
|
await executeRenderPipeline({
|
|
@@ -12589,7 +12897,7 @@ async function executeRenderPipeline(input) {
|
|
|
12589
12897
|
imageDecodeFailures: 0
|
|
12590
12898
|
};
|
|
12591
12899
|
let hdrPerf;
|
|
12592
|
-
const perfOutputPath =
|
|
12900
|
+
const perfOutputPath = join27(workDir, "perf-summary.json");
|
|
12593
12901
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
12594
12902
|
const observability = new RenderObservabilityRecorder({
|
|
12595
12903
|
pipelineStartMs: pipelineStart,
|
|
@@ -12662,7 +12970,7 @@ async function executeRenderPipeline(input) {
|
|
|
12662
12970
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
12663
12971
|
assertNotAborted();
|
|
12664
12972
|
assertConfiguredFfmpegBinariesExist();
|
|
12665
|
-
if (!existsSync16(workDir))
|
|
12973
|
+
if (!existsSync16(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
12666
12974
|
if (job.config.debug) {
|
|
12667
12975
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
12668
12976
|
}
|
|
@@ -12691,22 +12999,22 @@ async function executeRenderPipeline(input) {
|
|
|
12691
12999
|
requestedWorkers: job.config.workers ?? "auto"
|
|
12692
13000
|
});
|
|
12693
13001
|
const entryFile = job.config.entryFile || "index.html";
|
|
12694
|
-
let htmlPath =
|
|
13002
|
+
let htmlPath = join27(projectDir, entryFile);
|
|
12695
13003
|
if (!existsSync16(htmlPath)) {
|
|
12696
13004
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
12697
13005
|
}
|
|
12698
13006
|
assertNotAborted();
|
|
12699
|
-
const rawEntry =
|
|
13007
|
+
const rawEntry = readFileSync9(htmlPath, "utf-8");
|
|
12700
13008
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
12701
|
-
const wrapperPath =
|
|
12702
|
-
const projectIndexPath =
|
|
13009
|
+
const wrapperPath = join27(workDir, "standalone-entry.html");
|
|
13010
|
+
const projectIndexPath = join27(projectDir, "index.html");
|
|
12703
13011
|
if (!existsSync16(projectIndexPath)) {
|
|
12704
13012
|
throw new Error(
|
|
12705
13013
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
12706
13014
|
);
|
|
12707
13015
|
}
|
|
12708
13016
|
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
12709
|
-
|
|
13017
|
+
readFileSync9(projectIndexPath, "utf-8"),
|
|
12710
13018
|
entryFile,
|
|
12711
13019
|
rawEntry
|
|
12712
13020
|
);
|
|
@@ -12715,7 +13023,7 @@ async function executeRenderPipeline(input) {
|
|
|
12715
13023
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
12716
13024
|
);
|
|
12717
13025
|
}
|
|
12718
|
-
|
|
13026
|
+
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
12719
13027
|
htmlPath = wrapperPath;
|
|
12720
13028
|
log.info("Extracted standalone entry from index.html host context", {
|
|
12721
13029
|
entryFile
|
|
@@ -12862,7 +13170,7 @@ async function executeRenderPipeline(input) {
|
|
|
12862
13170
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
12863
13171
|
code: "extract_video"
|
|
12864
13172
|
});
|
|
12865
|
-
const compiledDir =
|
|
13173
|
+
const compiledDir = join27(workDir, "compiled");
|
|
12866
13174
|
const extractResult = await observeRenderStage(
|
|
12867
13175
|
observability,
|
|
12868
13176
|
"video_extract",
|
|
@@ -12996,7 +13304,7 @@ async function executeRenderPipeline(input) {
|
|
|
12996
13304
|
try {
|
|
12997
13305
|
fileServer = await createFileServer({
|
|
12998
13306
|
projectDir,
|
|
12999
|
-
compiledDir:
|
|
13307
|
+
compiledDir: join27(workDir, "compiled"),
|
|
13000
13308
|
port: 0,
|
|
13001
13309
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
13002
13310
|
fps: job.config.fps
|
|
@@ -13014,8 +13322,8 @@ async function executeRenderPipeline(input) {
|
|
|
13014
13322
|
if (!activeFileServer) {
|
|
13015
13323
|
throw new Error("File server failed to initialize before frame capture");
|
|
13016
13324
|
}
|
|
13017
|
-
const framesDir =
|
|
13018
|
-
if (!existsSync16(framesDir))
|
|
13325
|
+
const framesDir = join27(workDir, "captured-frames");
|
|
13326
|
+
if (!existsSync16(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13019
13327
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13020
13328
|
code: "start_browsers"
|
|
13021
13329
|
});
|
|
@@ -13486,7 +13794,7 @@ async function executeRenderPipeline(input) {
|
|
|
13486
13794
|
hls: ".mp4"
|
|
13487
13795
|
};
|
|
13488
13796
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
13489
|
-
const videoOnlyPath =
|
|
13797
|
+
const videoOnlyPath = join27(workDir, `video-only${videoExt}`);
|
|
13490
13798
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
13491
13799
|
if (usePageSideCompositingForTransitions) {
|
|
13492
13800
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -13739,7 +14047,7 @@ async function executeRenderPipeline(input) {
|
|
|
13739
14047
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
13740
14048
|
captureQuality: captureOptions.quality
|
|
13741
14049
|
});
|
|
13742
|
-
const segmentDir = segmentDirFor(
|
|
14050
|
+
const segmentDir = segmentDirFor(join27(projectDir, "renders"), segmentPlanHash);
|
|
13743
14051
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
13744
14052
|
if (job.config.resumeSegments === true) {
|
|
13745
14053
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -13754,10 +14062,10 @@ async function executeRenderPipeline(input) {
|
|
|
13754
14062
|
});
|
|
13755
14063
|
}
|
|
13756
14064
|
if (completedSegments.size === 0) {
|
|
13757
|
-
|
|
14065
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13758
14066
|
}
|
|
13759
14067
|
} else {
|
|
13760
|
-
|
|
14068
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13761
14069
|
}
|
|
13762
14070
|
const segmentManifest = readSegmentManifest(segmentDir) ?? {
|
|
13763
14071
|
version: 1,
|
|
@@ -13838,7 +14146,7 @@ async function executeRenderPipeline(input) {
|
|
|
13838
14146
|
);
|
|
13839
14147
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
13840
14148
|
if (job.config.keepSegments !== true) {
|
|
13841
|
-
|
|
14149
|
+
rmSync12(segmentDir, { recursive: true, force: true });
|
|
13842
14150
|
}
|
|
13843
14151
|
} else {
|
|
13844
14152
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
@@ -14110,8 +14418,8 @@ async function executeRenderPipeline(input) {
|
|
|
14110
14418
|
"capture_disk",
|
|
14111
14419
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
14112
14420
|
);
|
|
14113
|
-
|
|
14114
|
-
|
|
14421
|
+
rmSync12(framesDir, { recursive: true, force: true });
|
|
14422
|
+
mkdirSync10(framesDir, { recursive: true });
|
|
14115
14423
|
resetCaptureAttemptProgress(job);
|
|
14116
14424
|
dedupPerfs.length = 0;
|
|
14117
14425
|
cfg.useDrawElement = false;
|
|
@@ -14287,7 +14595,7 @@ async function executeRenderPipeline(input) {
|
|
|
14287
14595
|
job.perfSummary = perfSummary;
|
|
14288
14596
|
if (job.config.debug) {
|
|
14289
14597
|
try {
|
|
14290
|
-
|
|
14598
|
+
writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
14291
14599
|
} catch (err) {
|
|
14292
14600
|
log.debug("Failed to write perf summary", {
|
|
14293
14601
|
perfOutputPath,
|
|
@@ -14297,8 +14605,8 @@ async function executeRenderPipeline(input) {
|
|
|
14297
14605
|
}
|
|
14298
14606
|
if (job.config.debug) {
|
|
14299
14607
|
if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
|
|
14300
|
-
const debugOutput =
|
|
14301
|
-
|
|
14608
|
+
const debugOutput = join27(workDir, `output${videoExt}`);
|
|
14609
|
+
copyFileSync5(stagedOutputPath, debugOutput);
|
|
14302
14610
|
}
|
|
14303
14611
|
}
|
|
14304
14612
|
await commitArtifactTransaction(artifactTransaction, () => {
|
|
@@ -14422,15 +14730,15 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
|
14422
14730
|
// src/services/distributed/plan.ts
|
|
14423
14731
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
14424
14732
|
import {
|
|
14425
|
-
copyFileSync as
|
|
14733
|
+
copyFileSync as copyFileSync6,
|
|
14426
14734
|
cpSync as cpSync2,
|
|
14427
14735
|
existsSync as existsSync21,
|
|
14428
14736
|
linkSync,
|
|
14429
|
-
mkdirSync as
|
|
14430
|
-
rmSync as
|
|
14431
|
-
writeFileSync as
|
|
14737
|
+
mkdirSync as mkdirSync14,
|
|
14738
|
+
rmSync as rmSync14,
|
|
14739
|
+
writeFileSync as writeFileSync9
|
|
14432
14740
|
} from "node:fs";
|
|
14433
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
14741
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join35, relative as relative5, sep as sep2 } from "node:path";
|
|
14434
14742
|
import {
|
|
14435
14743
|
fpsToNumber as fpsToNumber10,
|
|
14436
14744
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -14440,12 +14748,12 @@ import {
|
|
|
14440
14748
|
normalizeVp9CpuUsed,
|
|
14441
14749
|
resolveConfig as resolveConfig3,
|
|
14442
14750
|
declareContentAddressedUrl,
|
|
14443
|
-
renameWithRetry as
|
|
14751
|
+
renameWithRetry as renameWithRetry7,
|
|
14444
14752
|
declareProbeFactsUrl
|
|
14445
14753
|
} from "@hyperframes/engine";
|
|
14446
14754
|
|
|
14447
14755
|
// src/services/distributed/planFrameCost.ts
|
|
14448
|
-
import { join as
|
|
14756
|
+
import { join as join30 } from "node:path";
|
|
14449
14757
|
import {
|
|
14450
14758
|
closeCaptureSession as closeCaptureSession9,
|
|
14451
14759
|
createCaptureSession as createCaptureSession9,
|
|
@@ -14455,7 +14763,7 @@ import {
|
|
|
14455
14763
|
|
|
14456
14764
|
// src/services/distributed/chunkFrames.ts
|
|
14457
14765
|
import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
|
|
14458
|
-
import { extname as extname4, join as
|
|
14766
|
+
import { extname as extname4, join as join29 } from "node:path";
|
|
14459
14767
|
import {
|
|
14460
14768
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
14461
14769
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -14465,8 +14773,8 @@ import {
|
|
|
14465
14773
|
|
|
14466
14774
|
// src/services/distributed/shared.ts
|
|
14467
14775
|
import { execFile as execFileCallback } from "node:child_process";
|
|
14468
|
-
import { dirname as
|
|
14469
|
-
import { existsSync as existsSync17, readFileSync as
|
|
14776
|
+
import { dirname as dirname12, join as join28 } from "node:path";
|
|
14777
|
+
import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
|
|
14470
14778
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
14471
14779
|
import { promisify as promisify2 } from "node:util";
|
|
14472
14780
|
import {
|
|
@@ -14481,7 +14789,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
14481
14789
|
}
|
|
14482
14790
|
function resolvePlanAudioPath(planDir) {
|
|
14483
14791
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
14484
|
-
const candidate =
|
|
14792
|
+
const candidate = join28(planDir, name);
|
|
14485
14793
|
if (existsSync17(candidate)) return candidate;
|
|
14486
14794
|
}
|
|
14487
14795
|
return null;
|
|
@@ -14738,13 +15046,13 @@ function buildSyntheticRenderJob(input) {
|
|
|
14738
15046
|
var cachedProducerVersion = null;
|
|
14739
15047
|
function readProducerVersion() {
|
|
14740
15048
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
14741
|
-
const startDir =
|
|
15049
|
+
const startDir = dirname12(fileURLToPath5(import.meta.url));
|
|
14742
15050
|
let current = startDir;
|
|
14743
15051
|
for (let i = 0; i < 10; i++) {
|
|
14744
|
-
const candidate =
|
|
15052
|
+
const candidate = join28(current, "package.json");
|
|
14745
15053
|
if (existsSync17(candidate)) {
|
|
14746
15054
|
try {
|
|
14747
|
-
const pkg = JSON.parse(
|
|
15055
|
+
const pkg = JSON.parse(readFileSync10(candidate, "utf-8"));
|
|
14748
15056
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
14749
15057
|
cachedProducerVersion = pkg.version;
|
|
14750
15058
|
return pkg.version;
|
|
@@ -14752,7 +15060,7 @@ function readProducerVersion() {
|
|
|
14752
15060
|
} catch {
|
|
14753
15061
|
}
|
|
14754
15062
|
}
|
|
14755
|
-
const parent =
|
|
15063
|
+
const parent = dirname12(current);
|
|
14756
15064
|
if (parent === current) break;
|
|
14757
15065
|
current = parent;
|
|
14758
15066
|
}
|
|
@@ -14795,7 +15103,7 @@ function frameNumberFromFileName(name) {
|
|
|
14795
15103
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
14796
15104
|
const result = [];
|
|
14797
15105
|
for (const v of videos) {
|
|
14798
|
-
const outputDir =
|
|
15106
|
+
const outputDir = join29(planDir, "video-frames", v.videoId);
|
|
14799
15107
|
if (!existsSync18(outputDir)) {
|
|
14800
15108
|
throw new Error(
|
|
14801
15109
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -14814,7 +15122,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
14814
15122
|
if (!frameName) continue;
|
|
14815
15123
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
14816
15124
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
14817
|
-
framePaths.set(frameIndex,
|
|
15125
|
+
framePaths.set(frameIndex, join29(outputDir, frameName));
|
|
14818
15126
|
}
|
|
14819
15127
|
result.push({
|
|
14820
15128
|
videoId: v.videoId,
|
|
@@ -14904,14 +15212,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14904
15212
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
14905
15213
|
return Promise.all(
|
|
14906
15214
|
deferred.map(async (video) => {
|
|
14907
|
-
const outputDir =
|
|
15215
|
+
const outputDir = join29(input.outputRoot, video.videoId);
|
|
14908
15216
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
14909
15217
|
if (indexes.length === 0) {
|
|
14910
15218
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
14911
15219
|
}
|
|
14912
15220
|
const range = video.deferredRange;
|
|
14913
15221
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
14914
|
-
const sourcePath = remoteUrl ??
|
|
15222
|
+
const sourcePath = remoteUrl ?? join29(input.planDir, ...range.sourcePath.split("/"));
|
|
14915
15223
|
if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
|
|
14916
15224
|
throw new RenderChunkValidationError(
|
|
14917
15225
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -14960,12 +15268,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14960
15268
|
planVideos,
|
|
14961
15269
|
frames: sampled,
|
|
14962
15270
|
fps: fpsRatio,
|
|
14963
|
-
outputRoot:
|
|
15271
|
+
outputRoot: join30(workDir, "frame-cost-video-frames"),
|
|
14964
15272
|
cfg: input.cfg
|
|
14965
15273
|
})
|
|
14966
15274
|
];
|
|
14967
15275
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
14968
|
-
const compiledDir =
|
|
15276
|
+
const compiledDir = join30(planDir, "compiled");
|
|
14969
15277
|
const fileServer = await createFileServer({
|
|
14970
15278
|
projectDir: compiledDir,
|
|
14971
15279
|
compiledDir,
|
|
@@ -14977,7 +15285,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14977
15285
|
try {
|
|
14978
15286
|
session = await createCaptureSession9(
|
|
14979
15287
|
fileServer.url,
|
|
14980
|
-
|
|
15288
|
+
join30(workDir, "frame-cost"),
|
|
14981
15289
|
buildChunkCaptureOptions({
|
|
14982
15290
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
14983
15291
|
encoder: input,
|
|
@@ -15074,8 +15382,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
15074
15382
|
}
|
|
15075
15383
|
|
|
15076
15384
|
// src/services/render/stages/freezePlan.ts
|
|
15077
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
15078
|
-
import { join as
|
|
15385
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
15386
|
+
import { join as join31, relative as relative3, resolve as resolve9 } from "node:path";
|
|
15079
15387
|
|
|
15080
15388
|
// src/services/distributed/planProtocol.ts
|
|
15081
15389
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -15173,12 +15481,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
15173
15481
|
}
|
|
15174
15482
|
|
|
15175
15483
|
// src/services/render/stages/planHash.ts
|
|
15176
|
-
import { createHash as
|
|
15177
|
-
import { closeSync as
|
|
15484
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
15485
|
+
import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync5, readSync as readSync4 } from "node:fs";
|
|
15178
15486
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
15179
15487
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
15180
15488
|
function computePlanHash(input) {
|
|
15181
|
-
const hash =
|
|
15489
|
+
const hash = createHash7("sha256");
|
|
15182
15490
|
hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
|
|
15183
15491
|
hash.update(input.compositionHtml);
|
|
15184
15492
|
hash.update(FIELD_DELIMITER);
|
|
@@ -15225,27 +15533,27 @@ function canonicalJsonStringify(value) {
|
|
|
15225
15533
|
throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
|
|
15226
15534
|
}
|
|
15227
15535
|
function sha256Hex(bytes) {
|
|
15228
|
-
const h =
|
|
15536
|
+
const h = createHash7("sha256");
|
|
15229
15537
|
h.update(bytes);
|
|
15230
15538
|
return h.digest("hex");
|
|
15231
15539
|
}
|
|
15232
15540
|
function sha256File(path) {
|
|
15233
|
-
const hash =
|
|
15541
|
+
const hash = createHash7("sha256");
|
|
15234
15542
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
15235
|
-
const fd =
|
|
15543
|
+
const fd = openSync5(path, "r");
|
|
15236
15544
|
try {
|
|
15237
15545
|
let bytesRead = 0;
|
|
15238
15546
|
do {
|
|
15239
|
-
bytesRead =
|
|
15547
|
+
bytesRead = readSync4(fd, buffer, 0, buffer.byteLength, null);
|
|
15240
15548
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
15241
15549
|
} while (bytesRead > 0);
|
|
15242
15550
|
} finally {
|
|
15243
|
-
|
|
15551
|
+
closeSync5(fd);
|
|
15244
15552
|
}
|
|
15245
15553
|
return hash.digest("hex");
|
|
15246
15554
|
}
|
|
15247
15555
|
async function sha256FileAsync(path) {
|
|
15248
|
-
const hash =
|
|
15556
|
+
const hash = createHash7("sha256");
|
|
15249
15557
|
for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
|
|
15250
15558
|
hash.update(chunk);
|
|
15251
15559
|
}
|
|
@@ -15289,11 +15597,11 @@ function stripUndefined(value) {
|
|
|
15289
15597
|
}
|
|
15290
15598
|
function listPlanFiles(planDir) {
|
|
15291
15599
|
const results = [];
|
|
15292
|
-
const rootResolved =
|
|
15600
|
+
const rootResolved = resolve9(planDir);
|
|
15293
15601
|
function walk(dir) {
|
|
15294
15602
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
15295
15603
|
for (const entry of entries) {
|
|
15296
|
-
const full =
|
|
15604
|
+
const full = join31(dir, entry.name);
|
|
15297
15605
|
if (entry.isDirectory()) {
|
|
15298
15606
|
walk(full);
|
|
15299
15607
|
} else if (entry.isFile()) {
|
|
@@ -15314,7 +15622,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
15314
15622
|
const assets = [];
|
|
15315
15623
|
for (const file of files) {
|
|
15316
15624
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
15317
|
-
compositionHtml =
|
|
15625
|
+
compositionHtml = readFileSync11(file.absolutePath);
|
|
15318
15626
|
continue;
|
|
15319
15627
|
}
|
|
15320
15628
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -15334,23 +15642,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
15334
15642
|
function writePlanRemoteFiles(planDir, files) {
|
|
15335
15643
|
if (files.length === 0) return;
|
|
15336
15644
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
15337
|
-
|
|
15645
|
+
writeFileSync7(join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
15338
15646
|
}
|
|
15339
15647
|
function readPlanRemoteFiles(planDir) {
|
|
15340
|
-
const path =
|
|
15341
|
-
return existsSync19(path) ? JSON.parse(
|
|
15648
|
+
const path = join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
15649
|
+
return existsSync19(path) ? JSON.parse(readFileSync11(path, "utf-8")) : [];
|
|
15342
15650
|
}
|
|
15343
15651
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
15344
|
-
const planJsonPath =
|
|
15345
|
-
const encoderJsonPath =
|
|
15652
|
+
const planJsonPath = join31(planDir, "plan.json");
|
|
15653
|
+
const encoderJsonPath = join31(planDir, "meta", "encoder.json");
|
|
15346
15654
|
if (!existsSync19(planJsonPath)) {
|
|
15347
15655
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
15348
15656
|
}
|
|
15349
15657
|
if (!existsSync19(encoderJsonPath)) {
|
|
15350
15658
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
15351
15659
|
}
|
|
15352
|
-
const planJson = JSON.parse(
|
|
15353
|
-
const encoderConfigCanonicalJson =
|
|
15660
|
+
const planJson = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
15661
|
+
const encoderConfigCanonicalJson = readFileSync11(encoderJsonPath, "utf-8");
|
|
15354
15662
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
15355
15663
|
return computePlanHash({
|
|
15356
15664
|
compositionHtml,
|
|
@@ -15379,18 +15687,18 @@ async function freezePlan(input) {
|
|
|
15379
15687
|
if (!existsSync19(planDir)) {
|
|
15380
15688
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
15381
15689
|
}
|
|
15382
|
-
const metaDir =
|
|
15383
|
-
if (!existsSync19(metaDir))
|
|
15384
|
-
|
|
15385
|
-
|
|
15690
|
+
const metaDir = join31(planDir, "meta");
|
|
15691
|
+
if (!existsSync19(metaDir)) mkdirSync11(metaDir, { recursive: true });
|
|
15692
|
+
writeFileSync7(
|
|
15693
|
+
join31(metaDir, "composition.json"),
|
|
15386
15694
|
`${JSON.stringify(composition, null, 2)}
|
|
15387
15695
|
`,
|
|
15388
15696
|
"utf-8"
|
|
15389
15697
|
);
|
|
15390
15698
|
const encoderForCanonical = stripUndefined(encoder);
|
|
15391
15699
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
15392
|
-
|
|
15393
|
-
|
|
15700
|
+
writeFileSync7(join31(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
15701
|
+
writeFileSync7(join31(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
15394
15702
|
`, "utf-8");
|
|
15395
15703
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
15396
15704
|
const planHash = computePlanHash({
|
|
@@ -15414,8 +15722,8 @@ async function freezePlan(input) {
|
|
|
15414
15722
|
duration: durationSeconds,
|
|
15415
15723
|
hasAudio
|
|
15416
15724
|
};
|
|
15417
|
-
const planJsonPath =
|
|
15418
|
-
|
|
15725
|
+
const planJsonPath = join31(planDir, "plan.json");
|
|
15726
|
+
writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
15419
15727
|
`, "utf-8");
|
|
15420
15728
|
return { planJsonPath, planHash };
|
|
15421
15729
|
}
|
|
@@ -15456,16 +15764,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
15456
15764
|
}
|
|
15457
15765
|
|
|
15458
15766
|
// src/services/distributed/chunkAudio.ts
|
|
15459
|
-
import { closeSync as
|
|
15460
|
-
import { writeFileSync as
|
|
15461
|
-
import { join as
|
|
15462
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as
|
|
15767
|
+
import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync12 } from "node:fs";
|
|
15768
|
+
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
15769
|
+
import { join as join33 } from "node:path";
|
|
15770
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
15463
15771
|
|
|
15464
15772
|
// src/services/distributed/mp4Stitch.ts
|
|
15465
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as
|
|
15773
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync13 } from "node:fs";
|
|
15466
15774
|
import { open } from "node:fs/promises";
|
|
15467
|
-
import { renameWithRetry as
|
|
15468
|
-
import { dirname as
|
|
15775
|
+
import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
|
|
15776
|
+
import { dirname as dirname13, join as join32 } from "node:path";
|
|
15469
15777
|
import { finished, pipeline } from "node:stream/promises";
|
|
15470
15778
|
function boxAt(src, off, at, limit) {
|
|
15471
15779
|
let size = src.readUInt32BE(off);
|
|
@@ -15478,16 +15786,16 @@ function boxAt(src, off, at, limit) {
|
|
|
15478
15786
|
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
15479
15787
|
}
|
|
15480
15788
|
function childBoxes(buf, parent) {
|
|
15481
|
-
const
|
|
15482
|
-
for (let at = parent.bodyStart; at < parent.end; at =
|
|
15483
|
-
|
|
15789
|
+
const boxes2 = [];
|
|
15790
|
+
for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
|
|
15791
|
+
boxes2.push(boxAt(buf, at, at, parent.end));
|
|
15484
15792
|
}
|
|
15485
|
-
return
|
|
15793
|
+
return boxes2;
|
|
15486
15794
|
}
|
|
15487
15795
|
function findChild(buf, parent, type) {
|
|
15488
15796
|
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
15489
15797
|
}
|
|
15490
|
-
function
|
|
15798
|
+
function child2(buf, parent, type) {
|
|
15491
15799
|
const found = findChild(buf, parent, type);
|
|
15492
15800
|
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
15493
15801
|
return found;
|
|
@@ -15539,7 +15847,7 @@ function rows(buf, table, columns, countAt = 4) {
|
|
|
15539
15847
|
function readMediaTime(moov, trak) {
|
|
15540
15848
|
const edts = findChild(moov, trak, "edts");
|
|
15541
15849
|
if (!edts) return 0;
|
|
15542
|
-
const elst =
|
|
15850
|
+
const elst = child2(moov, edts, "elst");
|
|
15543
15851
|
const wide = moov[elst.bodyStart] === 1;
|
|
15544
15852
|
const entries = moov.readUInt32BE(elst.bodyStart + 4);
|
|
15545
15853
|
for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
|
|
@@ -15554,7 +15862,7 @@ function readSizes(moov, stsz) {
|
|
|
15554
15862
|
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
15555
15863
|
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
15556
15864
|
}
|
|
15557
|
-
function
|
|
15865
|
+
function readSync5(moov, stbl, sampleCount) {
|
|
15558
15866
|
const stss = findChild(moov, stbl, "stss");
|
|
15559
15867
|
if (!stss) return null;
|
|
15560
15868
|
const sync = Array(sampleCount).fill(false);
|
|
@@ -15564,10 +15872,10 @@ function readSync4(moov, stbl, sampleCount) {
|
|
|
15564
15872
|
function readChunkOffsets(moov, stbl) {
|
|
15565
15873
|
const stco = findChild(moov, stbl, "stco");
|
|
15566
15874
|
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
15567
|
-
return rows(moov,
|
|
15875
|
+
return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
15568
15876
|
}
|
|
15569
15877
|
function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
15570
|
-
const stsc = rows(moov,
|
|
15878
|
+
const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
|
|
15571
15879
|
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
15572
15880
|
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
15573
15881
|
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
@@ -15575,28 +15883,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
|
15575
15883
|
});
|
|
15576
15884
|
}
|
|
15577
15885
|
function readTrack(moov, trak) {
|
|
15578
|
-
const mdia =
|
|
15579
|
-
const hdlr =
|
|
15580
|
-
const stbl =
|
|
15886
|
+
const mdia = child2(moov, trak, "mdia");
|
|
15887
|
+
const hdlr = child2(moov, mdia, "hdlr");
|
|
15888
|
+
const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
|
|
15581
15889
|
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
15582
|
-
const stts = rows(moov,
|
|
15890
|
+
const stts = rows(moov, child2(moov, stbl, "stts"), 2);
|
|
15583
15891
|
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
15584
|
-
const sizes = readSizes(moov,
|
|
15892
|
+
const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
|
|
15585
15893
|
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
15586
15894
|
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
15587
15895
|
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
15588
15896
|
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
15589
15897
|
}
|
|
15590
|
-
const stsd =
|
|
15898
|
+
const stsd = child2(moov, stbl, "stsd");
|
|
15591
15899
|
return {
|
|
15592
15900
|
trak,
|
|
15593
15901
|
handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
|
|
15594
|
-
timescale: timescaleOf(moov,
|
|
15902
|
+
timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
|
|
15595
15903
|
mediaTime: readMediaTime(moov, trak),
|
|
15596
15904
|
stsd: moov.subarray(stsd.start, stsd.end),
|
|
15597
15905
|
durations,
|
|
15598
15906
|
sizes,
|
|
15599
|
-
sync:
|
|
15907
|
+
sync: readSync5(moov, stbl, sizes.length),
|
|
15600
15908
|
samplesPerChunk,
|
|
15601
15909
|
chunkOffsets
|
|
15602
15910
|
};
|
|
@@ -15737,9 +16045,9 @@ function buildStitchedHeader(layouts, audio) {
|
|
|
15737
16045
|
}
|
|
15738
16046
|
const template = layouts[0].moov;
|
|
15739
16047
|
const root = boxAt(template, 0, 0, template.length);
|
|
15740
|
-
const mvhd =
|
|
16048
|
+
const mvhd = child2(template, root, "mvhd");
|
|
15741
16049
|
const movieTimescale = timescaleOf(template, mvhd);
|
|
15742
|
-
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template,
|
|
16050
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
|
|
15743
16051
|
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
15744
16052
|
const buildMoov = (dataStart2) => {
|
|
15745
16053
|
const traks = plan2.map((track, t) => {
|
|
@@ -15829,18 +16137,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15829
16137
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
15830
16138
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
15831
16139
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15832
|
-
|
|
16140
|
+
mkdirSync12(dirname13(outputPath), { recursive: true });
|
|
15833
16141
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15834
|
-
const out = createWriteStream(
|
|
16142
|
+
const out = createWriteStream(join32(workDir, "out.mp4"));
|
|
15835
16143
|
try {
|
|
15836
16144
|
out.write(header);
|
|
15837
16145
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15838
16146
|
out.end();
|
|
15839
16147
|
await finished(out);
|
|
15840
|
-
|
|
16148
|
+
renameWithRetry6(join32(workDir, "out.mp4"), outputPath);
|
|
15841
16149
|
} finally {
|
|
15842
16150
|
out.destroy();
|
|
15843
|
-
|
|
16151
|
+
rmSync13(workDir, { recursive: true, force: true });
|
|
15844
16152
|
}
|
|
15845
16153
|
}
|
|
15846
16154
|
|
|
@@ -15929,9 +16237,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
15929
16237
|
offset += track.sizes[offsets.length - 1];
|
|
15930
16238
|
}
|
|
15931
16239
|
});
|
|
15932
|
-
const bytes =
|
|
16240
|
+
const bytes = readFileSync12(input.audioPath);
|
|
15933
16241
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
15934
|
-
|
|
16242
|
+
mkdirSync13(join33(input.planDir, SLICE_DIR), { recursive: true });
|
|
15935
16243
|
input.chunks.forEach((_, index) => {
|
|
15936
16244
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
15937
16245
|
if (!cut) return;
|
|
@@ -15939,18 +16247,18 @@ async function writeChunkAudioSlices(input) {
|
|
|
15939
16247
|
const start = offsets[cut.firstPacket];
|
|
15940
16248
|
layout.partStarts[index] = start;
|
|
15941
16249
|
const end = offsets[last] + track.sizes[last];
|
|
15942
|
-
|
|
16250
|
+
writeFileSync8(join33(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
15943
16251
|
});
|
|
15944
16252
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
15945
|
-
|
|
15946
|
-
|
|
16253
|
+
writeFileSync8(join33(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
16254
|
+
writeFileSync8(join33(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
15947
16255
|
}
|
|
15948
16256
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
15949
|
-
const layoutPath =
|
|
16257
|
+
const layoutPath = join33(planDir, SLICE_LAYOUT);
|
|
15950
16258
|
if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
15951
|
-
const layout = JSON.parse(
|
|
15952
|
-
const frame =
|
|
15953
|
-
const fd =
|
|
16259
|
+
const layout = JSON.parse(readFileSync12(layoutPath, "utf-8"));
|
|
16260
|
+
const frame = readFileSync12(join33(planDir, SLICE_FRAME));
|
|
16261
|
+
const fd = openSync6(scratchPath, "w");
|
|
15954
16262
|
try {
|
|
15955
16263
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
15956
16264
|
writeSync(
|
|
@@ -15962,12 +16270,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
15962
16270
|
);
|
|
15963
16271
|
const start = layout.partStarts[chunkIndex];
|
|
15964
16272
|
if (start !== void 0) {
|
|
15965
|
-
const part =
|
|
16273
|
+
const part = readFileSync12(join33(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
15966
16274
|
writeSync(fd, part, 0, part.length, start);
|
|
15967
16275
|
}
|
|
15968
16276
|
ftruncateSync(fd, layout.sizeBytes);
|
|
15969
16277
|
} finally {
|
|
15970
|
-
|
|
16278
|
+
closeSync6(fd);
|
|
15971
16279
|
}
|
|
15972
16280
|
return scratchPath;
|
|
15973
16281
|
}
|
|
@@ -15987,7 +16295,7 @@ async function muxChunkWithAudio(input) {
|
|
|
15987
16295
|
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
15988
16296
|
appendRenderProvenanceArgs(args, input.outputPath);
|
|
15989
16297
|
args.push("-y", input.outputPath);
|
|
15990
|
-
const result = await
|
|
16298
|
+
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
15991
16299
|
if (!result.success) {
|
|
15992
16300
|
throw new Error(
|
|
15993
16301
|
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
|
|
@@ -16016,9 +16324,9 @@ async function muxChunkWithAudio(input) {
|
|
|
16016
16324
|
}
|
|
16017
16325
|
|
|
16018
16326
|
// src/services/distributed/planSize.ts
|
|
16019
|
-
import { createHash as
|
|
16327
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
16020
16328
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
16021
|
-
import { extname as extname5, join as
|
|
16329
|
+
import { extname as extname5, join as join34, relative as relative4 } from "node:path";
|
|
16022
16330
|
var MAX_TOP_COMPONENTS = 10;
|
|
16023
16331
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16024
16332
|
".aac",
|
|
@@ -16046,7 +16354,7 @@ var PLAN_ROOT_CATEGORY = {
|
|
|
16046
16354
|
"plan.json": "metadata"
|
|
16047
16355
|
};
|
|
16048
16356
|
function hashComponent(value) {
|
|
16049
|
-
return
|
|
16357
|
+
return createHash8("sha256").update(value).digest("hex").slice(0, 12);
|
|
16050
16358
|
}
|
|
16051
16359
|
function classifyFile(segments, rootKind) {
|
|
16052
16360
|
const rootName = segments[0] ?? "";
|
|
@@ -16130,7 +16438,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
16130
16438
|
return;
|
|
16131
16439
|
}
|
|
16132
16440
|
for (const entry of entries) {
|
|
16133
|
-
const full =
|
|
16441
|
+
const full = join34(dir, entry.name);
|
|
16134
16442
|
if (entry.isDirectory()) {
|
|
16135
16443
|
walkRegularFiles(full, visit);
|
|
16136
16444
|
} else if (entry.isFile()) {
|
|
@@ -16342,13 +16650,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
16342
16650
|
}
|
|
16343
16651
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
16344
16652
|
"plan.json",
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16653
|
+
join35("meta", "composition.json"),
|
|
16654
|
+
join35("meta", "encoder.json"),
|
|
16655
|
+
join35("meta", "chunks.json")
|
|
16348
16656
|
];
|
|
16349
16657
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
16350
16658
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
16351
|
-
|
|
16659
|
+
rmSync14(join35(planDir, relativePath), { force: true });
|
|
16352
16660
|
}
|
|
16353
16661
|
}
|
|
16354
16662
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -16493,23 +16801,23 @@ function createVideoSourceShipper(input) {
|
|
|
16493
16801
|
const known = shipped.get(srcPath);
|
|
16494
16802
|
if (known) return known;
|
|
16495
16803
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
16496
|
-
const current = stagedRel === null ? srcPath :
|
|
16804
|
+
const current = stagedRel === null ? srcPath : join35(input.planDir, "video-frames", stagedRel);
|
|
16497
16805
|
let result = null;
|
|
16498
16806
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
16499
16807
|
const rel = pathInside(root, srcPath);
|
|
16500
|
-
if (rel !== null && (existsSync21(
|
|
16808
|
+
if (rel !== null && (existsSync21(join35(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
16501
16809
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
16502
16810
|
break;
|
|
16503
16811
|
}
|
|
16504
16812
|
}
|
|
16505
16813
|
if (result === null) {
|
|
16506
16814
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
16507
|
-
const destination =
|
|
16508
|
-
|
|
16815
|
+
const destination = join35(input.planDir, ...result.split("/"));
|
|
16816
|
+
mkdirSync14(join35(input.planDir, "video-sources"), { recursive: true });
|
|
16509
16817
|
try {
|
|
16510
16818
|
linkSync(current, destination);
|
|
16511
16819
|
} catch {
|
|
16512
|
-
|
|
16820
|
+
copyFileSync6(current, destination);
|
|
16513
16821
|
}
|
|
16514
16822
|
}
|
|
16515
16823
|
shipped.set(srcPath, result);
|
|
@@ -16525,7 +16833,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16525
16833
|
useGpu: false,
|
|
16526
16834
|
browserGpuMode: "software"
|
|
16527
16835
|
});
|
|
16528
|
-
if (!existsSync21(planDir))
|
|
16836
|
+
if (!existsSync21(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
16529
16837
|
const log = config.logger ?? defaultLogger;
|
|
16530
16838
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
16531
16839
|
const abortSignal = config.abortSignal;
|
|
@@ -16554,15 +16862,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16554
16862
|
variables: config.variables
|
|
16555
16863
|
});
|
|
16556
16864
|
const entryFile = config.entryFile ?? "index.html";
|
|
16557
|
-
const htmlPath =
|
|
16865
|
+
const htmlPath = join35(projectDir, entryFile);
|
|
16558
16866
|
if (!existsSync21(htmlPath)) {
|
|
16559
16867
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16560
16868
|
}
|
|
16561
|
-
const workDir =
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
const compiledDir =
|
|
16565
|
-
|
|
16869
|
+
const workDir = join35(planDir, ".plan-work");
|
|
16870
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
16871
|
+
mkdirSync14(workDir, { recursive: true });
|
|
16872
|
+
const compiledDir = join35(workDir, "compiled");
|
|
16873
|
+
mkdirSync14(compiledDir, { recursive: true });
|
|
16566
16874
|
let stageStarted = Date.now();
|
|
16567
16875
|
cpSync2(projectDir, compiledDir, {
|
|
16568
16876
|
recursive: true,
|
|
@@ -16575,10 +16883,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16575
16883
|
}
|
|
16576
16884
|
});
|
|
16577
16885
|
logPlanStage(log, "copy", stageStarted);
|
|
16578
|
-
const finalCompiledDir =
|
|
16886
|
+
const finalCompiledDir = join35(planDir, "compiled");
|
|
16579
16887
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
16580
16888
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
16581
|
-
const remoteLocalPath = (rel) =>
|
|
16889
|
+
const remoteLocalPath = (rel) => join35(projectDir, ...rel.split("/"));
|
|
16582
16890
|
for (const [, file] of remoteFiles) {
|
|
16583
16891
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
16584
16892
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -16764,18 +17072,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16764
17072
|
let stageStarted = Date.now();
|
|
16765
17073
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
16766
17074
|
logPlanStage(log, "extract", stageStarted);
|
|
16767
|
-
const stagedVideoFrames =
|
|
16768
|
-
const videoFramesDst =
|
|
16769
|
-
if (existsSync21(videoFramesDst))
|
|
16770
|
-
const videoSourcesDir =
|
|
16771
|
-
|
|
17075
|
+
const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
|
|
17076
|
+
const videoFramesDst = join35(planDir, "video-frames");
|
|
17077
|
+
if (existsSync21(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
|
|
17078
|
+
const videoSourcesDir = join35(planDir, "video-sources");
|
|
17079
|
+
rmSync14(videoSourcesDir, { recursive: true, force: true });
|
|
16772
17080
|
if (existsSync21(stagedVideoFrames)) {
|
|
16773
|
-
|
|
17081
|
+
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
16774
17082
|
} else {
|
|
16775
|
-
|
|
17083
|
+
mkdirSync14(videoFramesDst, { recursive: true });
|
|
16776
17084
|
}
|
|
16777
|
-
if (existsSync21(finalCompiledDir))
|
|
16778
|
-
|
|
17085
|
+
if (existsSync21(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
|
|
17086
|
+
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
16779
17087
|
const shipVideoSource = createVideoSourceShipper({
|
|
16780
17088
|
planDir,
|
|
16781
17089
|
projectDir,
|
|
@@ -16804,7 +17112,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16804
17112
|
};
|
|
16805
17113
|
})
|
|
16806
17114
|
});
|
|
16807
|
-
|
|
17115
|
+
mkdirSync14(join35(planDir, "meta"), { recursive: true });
|
|
16808
17116
|
writePlanRemoteFiles(
|
|
16809
17117
|
planDir,
|
|
16810
17118
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -16813,8 +17121,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16813
17121
|
sizeBytes
|
|
16814
17122
|
}))
|
|
16815
17123
|
);
|
|
16816
|
-
|
|
16817
|
-
|
|
17124
|
+
writeFileSync9(
|
|
17125
|
+
join35(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
16818
17126
|
JSON.stringify(planVideosJson, null, 2),
|
|
16819
17127
|
"utf-8"
|
|
16820
17128
|
);
|
|
@@ -16893,9 +17201,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16893
17201
|
if (audioResult?.audioError) {
|
|
16894
17202
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
16895
17203
|
}
|
|
16896
|
-
const planAudioPath =
|
|
17204
|
+
const planAudioPath = join35(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
16897
17205
|
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
16898
|
-
|
|
17206
|
+
renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
|
|
16899
17207
|
}
|
|
16900
17208
|
if (chunksCarryAudio && existsSync21(planAudioPath)) {
|
|
16901
17209
|
if (options.sliceChunkAudio) {
|
|
@@ -16942,7 +17250,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
16942
17250
|
format: config.format
|
|
16943
17251
|
};
|
|
16944
17252
|
try {
|
|
16945
|
-
|
|
17253
|
+
rmSync14(workDir, { recursive: true, force: true });
|
|
16946
17254
|
} catch (err) {
|
|
16947
17255
|
log.warn("[plan] failed to remove temp work dir", {
|
|
16948
17256
|
workDir,
|
|
@@ -17513,14 +17821,14 @@ import {
|
|
|
17513
17821
|
// src/server.ts
|
|
17514
17822
|
import {
|
|
17515
17823
|
existsSync as existsSync24,
|
|
17516
|
-
mkdirSync as
|
|
17517
|
-
statSync as
|
|
17824
|
+
mkdirSync as mkdirSync15,
|
|
17825
|
+
statSync as statSync10,
|
|
17518
17826
|
mkdtempSync as mkdtempSync5,
|
|
17519
|
-
writeFileSync as
|
|
17520
|
-
rmSync as
|
|
17827
|
+
writeFileSync as writeFileSync10,
|
|
17828
|
+
rmSync as rmSync15,
|
|
17521
17829
|
createReadStream as createReadStream5
|
|
17522
17830
|
} from "node:fs";
|
|
17523
|
-
import { resolve as
|
|
17831
|
+
import { resolve as resolve11, dirname as dirname15, join as join38 } from "node:path";
|
|
17524
17832
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17525
17833
|
import { parseArgs } from "node:util";
|
|
17526
17834
|
import crypto from "node:crypto";
|
|
@@ -17532,14 +17840,14 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
17532
17840
|
// src/services/hyperframeLint.ts
|
|
17533
17841
|
import {
|
|
17534
17842
|
existsSync as existsSync22,
|
|
17535
|
-
readFileSync as
|
|
17536
|
-
statSync as
|
|
17537
|
-
openSync as
|
|
17843
|
+
readFileSync as readFileSync13,
|
|
17844
|
+
statSync as statSync9,
|
|
17845
|
+
openSync as openSync7,
|
|
17538
17846
|
fstatSync as fstatSync3,
|
|
17539
|
-
closeSync as
|
|
17847
|
+
closeSync as closeSync7,
|
|
17540
17848
|
constants as constants3
|
|
17541
17849
|
} from "node:fs";
|
|
17542
|
-
import { resolve as
|
|
17850
|
+
import { resolve as resolve10, join as join36, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
|
|
17543
17851
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
17544
17852
|
function isStringRecord(value) {
|
|
17545
17853
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -17569,30 +17877,30 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
17569
17877
|
function readEntryHtml(filePath) {
|
|
17570
17878
|
let fd;
|
|
17571
17879
|
try {
|
|
17572
|
-
fd =
|
|
17880
|
+
fd = openSync7(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
|
|
17573
17881
|
} catch (error) {
|
|
17574
17882
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
17575
17883
|
return null;
|
|
17576
|
-
if (!
|
|
17884
|
+
if (!statSync9(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
17577
17885
|
throw error;
|
|
17578
17886
|
}
|
|
17579
17887
|
try {
|
|
17580
17888
|
if (!fstatSync3(fd).isFile()) return null;
|
|
17581
|
-
return
|
|
17889
|
+
return readFileSync13(fd, "utf-8");
|
|
17582
17890
|
} finally {
|
|
17583
|
-
|
|
17891
|
+
closeSync7(fd);
|
|
17584
17892
|
}
|
|
17585
17893
|
}
|
|
17586
17894
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
17587
|
-
const absProjectDir =
|
|
17588
|
-
if (!existsSync22(absProjectDir) || !
|
|
17895
|
+
const absProjectDir = resolve10(projectDir);
|
|
17896
|
+
if (!existsSync22(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
17589
17897
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17590
17898
|
}
|
|
17591
17899
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
17592
17900
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
17593
17901
|
);
|
|
17594
17902
|
for (const entryFile of entryCandidates) {
|
|
17595
|
-
const absoluteEntryPath =
|
|
17903
|
+
const absoluteEntryPath = resolve10(absProjectDir, entryFile);
|
|
17596
17904
|
const entryRelativePath = relative6(absProjectDir, absoluteEntryPath);
|
|
17597
17905
|
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute4(entryRelativePath)) {
|
|
17598
17906
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
@@ -17607,7 +17915,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
17607
17915
|
}
|
|
17608
17916
|
}
|
|
17609
17917
|
return {
|
|
17610
|
-
error: `No HTML entry file found in project directory: ${
|
|
17918
|
+
error: `No HTML entry file found in project directory: ${join36(absProjectDir, preferredEntryFile || "index.html")}`
|
|
17611
17919
|
};
|
|
17612
17920
|
}
|
|
17613
17921
|
function prepareHyperframeLintBody(body) {
|
|
@@ -17653,7 +17961,7 @@ async function runHyperframeLint(prepared) {
|
|
|
17653
17961
|
// src/services/healthWorker.ts
|
|
17654
17962
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
17655
17963
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17656
|
-
import { dirname as
|
|
17964
|
+
import { dirname as dirname14, join as join37 } from "node:path";
|
|
17657
17965
|
import { existsSync as existsSync23 } from "node:fs";
|
|
17658
17966
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
17659
17967
|
async function startHealthWorker(options = {}) {
|
|
@@ -17672,12 +17980,12 @@ async function startHealthWorker(options = {}) {
|
|
|
17672
17980
|
stdout: false,
|
|
17673
17981
|
stderr: false
|
|
17674
17982
|
});
|
|
17675
|
-
await new Promise((
|
|
17983
|
+
await new Promise((resolve13, reject) => {
|
|
17676
17984
|
const onMessage = (msg) => {
|
|
17677
17985
|
if (msg?.type === "listening") {
|
|
17678
17986
|
worker.off("error", onError);
|
|
17679
17987
|
worker.off("message", onMessage);
|
|
17680
|
-
|
|
17988
|
+
resolve13();
|
|
17681
17989
|
} else if (msg?.type === "listen-error") {
|
|
17682
17990
|
worker.off("error", onError);
|
|
17683
17991
|
worker.off("message", onMessage);
|
|
@@ -17728,8 +18036,8 @@ function defaultLogger2() {
|
|
|
17728
18036
|
};
|
|
17729
18037
|
}
|
|
17730
18038
|
function resolveWorkerEntry2() {
|
|
17731
|
-
const here =
|
|
17732
|
-
const candidates = [
|
|
18039
|
+
const here = dirname14(fileURLToPath6(import.meta.url));
|
|
18040
|
+
const candidates = [join37(here, "healthWorkerThread.js"), join37(here, "healthWorkerThread.ts")];
|
|
17733
18041
|
for (const candidate of candidates) {
|
|
17734
18042
|
if (existsSync23(candidate)) return candidate;
|
|
17735
18043
|
}
|
|
@@ -17747,6 +18055,8 @@ var DEFAULT_SERVER_FPS = { num: 30, den: 1 };
|
|
|
17747
18055
|
var SSE_KEEPALIVE_MS = 3e4;
|
|
17748
18056
|
var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
17749
18057
|
"ASSET_MEDIA_TYPE_MISMATCH",
|
|
18058
|
+
"HEIF_IMAGE_CONVERSION_FAILED",
|
|
18059
|
+
"HEIF_TOOL_UNAVAILABLE",
|
|
17750
18060
|
"NOT_MEDIA_PAYLOAD",
|
|
17751
18061
|
"INVALID_VIDEO_METADATA",
|
|
17752
18062
|
"VIDEO_SOURCE_UNRENDERABLE",
|
|
@@ -17882,8 +18192,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
17882
18192
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
17883
18193
|
const { input, cleanupProjectDir } = prepared;
|
|
17884
18194
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
17885
|
-
const outputDir =
|
|
17886
|
-
if (!existsSync24(outputDir))
|
|
18195
|
+
const outputDir = dirname15(absoluteOutputPath);
|
|
18196
|
+
if (!existsSync24(outputDir)) mkdirSync15(outputDir, { recursive: true });
|
|
17887
18197
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
17888
18198
|
}
|
|
17889
18199
|
function validateRenderOverrides(body) {
|
|
@@ -17923,12 +18233,12 @@ var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
|
|
|
17923
18233
|
function prepareProjectDirectory(projectDir, options) {
|
|
17924
18234
|
const candidate = nonEmptyString(projectDir);
|
|
17925
18235
|
if (!candidate) return null;
|
|
17926
|
-
const absProjectDir =
|
|
17927
|
-
if (!existsSync24(absProjectDir) || !
|
|
18236
|
+
const absProjectDir = resolve11(candidate);
|
|
18237
|
+
if (!existsSync24(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
17928
18238
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
17929
18239
|
}
|
|
17930
18240
|
const entry = options.entryFile || "index.html";
|
|
17931
|
-
if (!existsSync24(
|
|
18241
|
+
if (!existsSync24(resolve11(absProjectDir, entry))) {
|
|
17932
18242
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
17933
18243
|
}
|
|
17934
18244
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -17949,8 +18259,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
17949
18259
|
}
|
|
17950
18260
|
function materializeInlineProject(html, options) {
|
|
17951
18261
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
17952
|
-
const tempProjectDir = mkdtempSync5(
|
|
17953
|
-
|
|
18262
|
+
const tempProjectDir = mkdtempSync5(join38(tempRoot, "producer-project-"));
|
|
18263
|
+
writeFileSync10(join38(tempProjectDir, "index.html"), html, "utf-8");
|
|
17954
18264
|
return {
|
|
17955
18265
|
prepared: {
|
|
17956
18266
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -17975,7 +18285,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
|
|
|
17975
18285
|
try {
|
|
17976
18286
|
return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
|
|
17977
18287
|
} catch (error) {
|
|
17978
|
-
const fallbackPath =
|
|
18288
|
+
const fallbackPath = resolve11(rendersDir, `producer-fallback-${Date.now()}.mp4`);
|
|
17979
18289
|
log.warn("Failed to resolve output path, using fallback", {
|
|
17980
18290
|
fallback: fallbackPath,
|
|
17981
18291
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -18011,7 +18321,7 @@ function createArtifactStore(ttlMs) {
|
|
|
18011
18321
|
function cleanupTempDir(dir, log) {
|
|
18012
18322
|
if (!dir) return;
|
|
18013
18323
|
try {
|
|
18014
|
-
|
|
18324
|
+
rmSync15(dir, { recursive: true, force: true });
|
|
18015
18325
|
} catch (error) {
|
|
18016
18326
|
log.warn("Failed to cleanup temp project dir", {
|
|
18017
18327
|
cleanupProjectDir: dir,
|
|
@@ -18020,7 +18330,7 @@ function cleanupTempDir(dir, log) {
|
|
|
18020
18330
|
}
|
|
18021
18331
|
}
|
|
18022
18332
|
function outputFileSize(path) {
|
|
18023
|
-
return existsSync24(path) ?
|
|
18333
|
+
return existsSync24(path) ? statSync10(path).size : 0;
|
|
18024
18334
|
}
|
|
18025
18335
|
function createBlockingProgressReporter(log, requestId) {
|
|
18026
18336
|
let lastLoggedPct = -10;
|
|
@@ -18326,7 +18636,7 @@ function createRenderHandlers(options = {}) {
|
|
|
18326
18636
|
store.delete(token);
|
|
18327
18637
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
18328
18638
|
}
|
|
18329
|
-
const stats =
|
|
18639
|
+
const stats = statSync10(artifact.path);
|
|
18330
18640
|
return new Response(createReadStream5(artifact.path), {
|
|
18331
18641
|
headers: {
|
|
18332
18642
|
"content-type": "video/mp4",
|
|
@@ -18393,7 +18703,7 @@ function startServer(options = {}) {
|
|
|
18393
18703
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
18394
18704
|
return server;
|
|
18395
18705
|
}
|
|
18396
|
-
var entryScript = process.argv[1] ?
|
|
18706
|
+
var entryScript = process.argv[1] ? resolve11(process.argv[1]) : "";
|
|
18397
18707
|
var isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
|
|
18398
18708
|
if (isPublicServerEntry) {
|
|
18399
18709
|
const { values } = parseArgs({
|
|
@@ -18418,35 +18728,35 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
|
|
|
18418
18728
|
|
|
18419
18729
|
// src/services/distributed/planV2.ts
|
|
18420
18730
|
import {
|
|
18421
|
-
copyFileSync as
|
|
18731
|
+
copyFileSync as copyFileSync8,
|
|
18422
18732
|
existsSync as existsSync26,
|
|
18423
18733
|
lstatSync as lstatSync3,
|
|
18424
|
-
mkdirSync as
|
|
18734
|
+
mkdirSync as mkdirSync17,
|
|
18425
18735
|
mkdtempSync as mkdtempSync7,
|
|
18426
|
-
readFileSync as
|
|
18736
|
+
readFileSync as readFileSync14,
|
|
18427
18737
|
readdirSync as readdirSync9,
|
|
18428
|
-
rmSync as
|
|
18429
|
-
statSync as
|
|
18430
|
-
writeFileSync as
|
|
18738
|
+
rmSync as rmSync17,
|
|
18739
|
+
statSync as statSync12,
|
|
18740
|
+
writeFileSync as writeFileSync12
|
|
18431
18741
|
} from "node:fs";
|
|
18432
18742
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
18433
|
-
import { dirname as
|
|
18743
|
+
import { dirname as dirname17, isAbsolute as isAbsolute5, join as join41, relative as relative7, resolve as resolve12, sep as sep4 } from "node:path";
|
|
18434
18744
|
import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
|
|
18435
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
18745
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
18436
18746
|
|
|
18437
18747
|
// src/services/distributed/planV2Publisher.ts
|
|
18438
18748
|
import {
|
|
18439
|
-
copyFileSync as
|
|
18749
|
+
copyFileSync as copyFileSync7,
|
|
18440
18750
|
existsSync as existsSync25,
|
|
18441
18751
|
linkSync as linkSync2,
|
|
18442
|
-
mkdirSync as
|
|
18752
|
+
mkdirSync as mkdirSync16,
|
|
18443
18753
|
mkdtempSync as mkdtempSync6,
|
|
18444
|
-
rmSync as
|
|
18445
|
-
statSync as
|
|
18446
|
-
writeFileSync as
|
|
18754
|
+
rmSync as rmSync16,
|
|
18755
|
+
statSync as statSync11,
|
|
18756
|
+
writeFileSync as writeFileSync11
|
|
18447
18757
|
} from "node:fs";
|
|
18448
|
-
import { renameWithRetry as
|
|
18449
|
-
import { dirname as
|
|
18758
|
+
import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
18759
|
+
import { dirname as dirname16, join as join40 } from "node:path";
|
|
18450
18760
|
|
|
18451
18761
|
// src/services/distributed/planV2Errors.ts
|
|
18452
18762
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -18461,7 +18771,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
18461
18771
|
};
|
|
18462
18772
|
|
|
18463
18773
|
// src/services/distributed/planV2Layout.ts
|
|
18464
|
-
import { join as
|
|
18774
|
+
import { join as join39 } from "node:path";
|
|
18465
18775
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18466
18776
|
function assertPlanV2Sha256(value, field) {
|
|
18467
18777
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -18471,7 +18781,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
18471
18781
|
}
|
|
18472
18782
|
function planV2BlobPath(root, sha256) {
|
|
18473
18783
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
18474
|
-
return
|
|
18784
|
+
return join39(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
18475
18785
|
}
|
|
18476
18786
|
|
|
18477
18787
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18511,12 +18821,12 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18511
18821
|
}
|
|
18512
18822
|
this.destinationDir = destinationDir;
|
|
18513
18823
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18514
|
-
|
|
18515
|
-
this.temporaryDir = mkdtempSync6(
|
|
18824
|
+
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18825
|
+
this.temporaryDir = mkdtempSync6(join40(dirname16(destinationDir), ".plan-v2-publish-"));
|
|
18516
18826
|
}
|
|
18517
18827
|
async putBlob(blob) {
|
|
18518
18828
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
18519
|
-
const sourceSize =
|
|
18829
|
+
const sourceSize = statSync11(blob.sourcePath).size;
|
|
18520
18830
|
if (sourceSize !== blob.sizeBytes) {
|
|
18521
18831
|
throw new PlanV2IntegrityError(
|
|
18522
18832
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -18524,19 +18834,19 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18524
18834
|
}
|
|
18525
18835
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18526
18836
|
if (existsSync25(destinationPath)) return;
|
|
18527
|
-
|
|
18528
|
-
const stagingDir = mkdtempSync6(
|
|
18529
|
-
const temporaryPath =
|
|
18837
|
+
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18838
|
+
const stagingDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18839
|
+
const temporaryPath = join40(stagingDir, "blob");
|
|
18530
18840
|
try {
|
|
18531
18841
|
try {
|
|
18532
18842
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
18533
18843
|
} catch (error) {
|
|
18534
18844
|
if (!canFallbackToCopy(error)) throw error;
|
|
18535
|
-
|
|
18845
|
+
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
18536
18846
|
}
|
|
18537
|
-
|
|
18847
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
18538
18848
|
} finally {
|
|
18539
|
-
|
|
18849
|
+
rmSync16(stagingDir, { recursive: true, force: true });
|
|
18540
18850
|
}
|
|
18541
18851
|
}
|
|
18542
18852
|
async commitManifest(manifestBytes) {
|
|
@@ -18547,13 +18857,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18547
18857
|
);
|
|
18548
18858
|
}
|
|
18549
18859
|
}
|
|
18550
|
-
|
|
18551
|
-
|
|
18860
|
+
writeFileSync11(join40(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18861
|
+
renameWithRetry8(this.temporaryDir, this.destinationDir);
|
|
18552
18862
|
this.#committed = true;
|
|
18553
18863
|
}
|
|
18554
18864
|
async abort() {
|
|
18555
18865
|
if (!this.#committed) {
|
|
18556
|
-
|
|
18866
|
+
rmSync16(this.temporaryDir, { recursive: true, force: true });
|
|
18557
18867
|
}
|
|
18558
18868
|
}
|
|
18559
18869
|
};
|
|
@@ -18572,7 +18882,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
18572
18882
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
18573
18883
|
}
|
|
18574
18884
|
function readJsonFile(path, label) {
|
|
18575
|
-
const contents =
|
|
18885
|
+
const contents = readFileSync14(path, "utf-8");
|
|
18576
18886
|
try {
|
|
18577
18887
|
const parsed = JSON.parse(contents);
|
|
18578
18888
|
return parsed;
|
|
@@ -18589,10 +18899,10 @@ function assertSafeRelativePath(path) {
|
|
|
18589
18899
|
}
|
|
18590
18900
|
function listFiles(root) {
|
|
18591
18901
|
const files = [];
|
|
18592
|
-
const rootResolved =
|
|
18902
|
+
const rootResolved = resolve12(root);
|
|
18593
18903
|
function walk(dir) {
|
|
18594
18904
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18595
|
-
const absolutePath =
|
|
18905
|
+
const absolutePath = join41(dir, entry.name);
|
|
18596
18906
|
if (entry.isDirectory()) {
|
|
18597
18907
|
walk(absolutePath);
|
|
18598
18908
|
} else if (entry.isFile()) {
|
|
@@ -18619,13 +18929,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18619
18929
|
}
|
|
18620
18930
|
}
|
|
18621
18931
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18622
|
-
const videoRoot =
|
|
18932
|
+
const videoRoot = join41(executionPlanDir, "video-frames");
|
|
18623
18933
|
if (!existsSync26(videoRoot)) return;
|
|
18624
18934
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18625
18935
|
if (!videoEntry.isDirectory()) continue;
|
|
18626
|
-
const videoDir =
|
|
18936
|
+
const videoDir = join41(videoRoot, videoEntry.name);
|
|
18627
18937
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18628
|
-
const sentinelPath =
|
|
18938
|
+
const sentinelPath = join41(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18629
18939
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18630
18940
|
}
|
|
18631
18941
|
}
|
|
@@ -18635,8 +18945,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
18635
18945
|
return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
|
|
18636
18946
|
}
|
|
18637
18947
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
18638
|
-
const videoRoot =
|
|
18639
|
-
const outputDir =
|
|
18948
|
+
const videoRoot = resolve12(planDir, "video-frames");
|
|
18949
|
+
const outputDir = resolve12(videoRoot, videoId);
|
|
18640
18950
|
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
|
|
18641
18951
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
18642
18952
|
}
|
|
@@ -18672,7 +18982,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18672
18982
|
if (video.deferredRange) {
|
|
18673
18983
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18674
18984
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18675
|
-
framePaths2.set(index,
|
|
18985
|
+
framePaths2.set(index, join41(outputDir, String(index)));
|
|
18676
18986
|
}
|
|
18677
18987
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18678
18988
|
}
|
|
@@ -18680,7 +18990,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18680
18990
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18681
18991
|
for (const frameName of frameNames) {
|
|
18682
18992
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18683
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18993
|
+
assertValidExtractionCacheCompleteSentinel(join41(outputDir, frameName));
|
|
18684
18994
|
continue;
|
|
18685
18995
|
}
|
|
18686
18996
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18699,7 +19009,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18699
19009
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18700
19010
|
);
|
|
18701
19011
|
}
|
|
18702
|
-
framePaths.set(frameIndex,
|
|
19012
|
+
framePaths.set(frameIndex, join41(outputDir, frameName));
|
|
18703
19013
|
}
|
|
18704
19014
|
return {
|
|
18705
19015
|
...video,
|
|
@@ -18720,11 +19030,11 @@ function parsePlanVideosJson2(value) {
|
|
|
18720
19030
|
}
|
|
18721
19031
|
}
|
|
18722
19032
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18723
|
-
const videosPath =
|
|
19033
|
+
const videosPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18724
19034
|
if (!existsSync26(videosPath)) return;
|
|
18725
19035
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18726
19036
|
for (const video of videos.extracted) {
|
|
18727
|
-
|
|
19037
|
+
mkdirSync17(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
18728
19038
|
}
|
|
18729
19039
|
}
|
|
18730
19040
|
function parseChunkSlices(value) {
|
|
@@ -18754,13 +19064,13 @@ function parseChunkSlices(value) {
|
|
|
18754
19064
|
});
|
|
18755
19065
|
}
|
|
18756
19066
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18757
|
-
const videoRoot =
|
|
19067
|
+
const videoRoot = join41(executionPlanDir, "video-frames");
|
|
18758
19068
|
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18759
|
-
const videosPath =
|
|
19069
|
+
const videosPath = join41(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18760
19070
|
if (!existsSync26(videosPath)) {
|
|
18761
19071
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18762
19072
|
}
|
|
18763
|
-
const chunksPath =
|
|
19073
|
+
const chunksPath = join41(executionPlanDir, "meta", "chunks.json");
|
|
18764
19074
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18765
19075
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18766
19076
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18797,7 +19107,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
18797
19107
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
18798
19108
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
18799
19109
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
18800
|
-
const path = input.deferredSources.get(videoId) ?? relative7(
|
|
19110
|
+
const path = input.deferredSources.get(videoId) ?? relative7(resolve12(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
18801
19111
|
assertSafeRelativePath(path);
|
|
18802
19112
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
18803
19113
|
indexes.add(chunk.index);
|
|
@@ -18815,21 +19125,21 @@ function computeManifestHash(manifest) {
|
|
|
18815
19125
|
}
|
|
18816
19126
|
function writeBlob(sourcePath, destinationPath) {
|
|
18817
19127
|
if (existsSync26(destinationPath)) return;
|
|
18818
|
-
|
|
18819
|
-
const temporaryDir = mkdtempSync7(
|
|
18820
|
-
const temporaryPath =
|
|
19128
|
+
mkdirSync17(dirname17(destinationPath), { recursive: true });
|
|
19129
|
+
const temporaryDir = mkdtempSync7(join41(dirname17(destinationPath), ".plan-v2-blob-"));
|
|
19130
|
+
const temporaryPath = join41(temporaryDir, "blob");
|
|
18821
19131
|
try {
|
|
18822
|
-
|
|
18823
|
-
|
|
19132
|
+
copyFileSync8(sourcePath, temporaryPath);
|
|
19133
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
18824
19134
|
} finally {
|
|
18825
|
-
|
|
19135
|
+
rmSync17(temporaryDir, { recursive: true, force: true });
|
|
18826
19136
|
}
|
|
18827
19137
|
}
|
|
18828
19138
|
function publishBlobFor(absolutePath, digests) {
|
|
18829
19139
|
return {
|
|
18830
19140
|
sourcePath: absolutePath,
|
|
18831
19141
|
sha256: planFileSha256(absolutePath, digests),
|
|
18832
|
-
sizeBytes:
|
|
19142
|
+
sizeBytes: statSync12(absolutePath).size
|
|
18833
19143
|
};
|
|
18834
19144
|
}
|
|
18835
19145
|
async function putBlobs(publisher, blobs) {
|
|
@@ -18853,7 +19163,7 @@ async function putTasks(tasks) {
|
|
|
18853
19163
|
if (failure) throw failure.reason;
|
|
18854
19164
|
}
|
|
18855
19165
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18856
|
-
const executionPlanPath =
|
|
19166
|
+
const executionPlanPath = join41(executionPlanDir, "plan.json");
|
|
18857
19167
|
if (!existsSync26(executionPlanPath)) {
|
|
18858
19168
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18859
19169
|
}
|
|
@@ -18881,7 +19191,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18881
19191
|
}
|
|
18882
19192
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18883
19193
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18884
|
-
const encoder = readJsonFile(
|
|
19194
|
+
const encoder = readJsonFile(join41(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18885
19195
|
const files = listFiles(executionPlanDir);
|
|
18886
19196
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18887
19197
|
for (const file of files) {
|
|
@@ -18941,21 +19251,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18941
19251
|
if (publication.storedBlobs.length > 0) {
|
|
18942
19252
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18943
19253
|
}
|
|
18944
|
-
|
|
18945
|
-
const tempDir = mkdtempSync7(
|
|
19254
|
+
mkdirSync17(dirname17(planV2Dir), { recursive: true });
|
|
19255
|
+
const tempDir = mkdtempSync7(join41(dirname17(planV2Dir), ".plan-v2-build-"));
|
|
18946
19256
|
try {
|
|
18947
19257
|
for (const blob of publication.blobs) {
|
|
18948
19258
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18949
19259
|
}
|
|
18950
|
-
|
|
18951
|
-
|
|
19260
|
+
writeFileSync12(
|
|
19261
|
+
join41(tempDir, "plan.json"),
|
|
18952
19262
|
canonicalJsonStringify(publication.manifest),
|
|
18953
19263
|
"utf-8"
|
|
18954
19264
|
);
|
|
18955
|
-
|
|
19265
|
+
renameWithRetry9(tempDir, planV2Dir);
|
|
18956
19266
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18957
19267
|
} catch (error) {
|
|
18958
|
-
|
|
19268
|
+
rmSync17(tempDir, { recursive: true, force: true });
|
|
18959
19269
|
throw error;
|
|
18960
19270
|
}
|
|
18961
19271
|
}
|
|
@@ -18995,7 +19305,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18995
19305
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18996
19306
|
}
|
|
18997
19307
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18998
|
-
const stagingRoot = mkdtempSync7(
|
|
19308
|
+
const stagingRoot = mkdtempSync7(join41(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
|
|
18999
19309
|
const digests = /* @__PURE__ */ new Map();
|
|
19000
19310
|
try {
|
|
19001
19311
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -19006,7 +19316,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
19006
19316
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
19007
19317
|
whileAudioMixes: () => putBlobs(
|
|
19008
19318
|
publisher,
|
|
19009
|
-
listFiles(
|
|
19319
|
+
listFiles(join41(stagingRoot, "compiled")).map(
|
|
19010
19320
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
19011
19321
|
)
|
|
19012
19322
|
),
|
|
@@ -19022,7 +19332,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
19022
19332
|
}
|
|
19023
19333
|
throw error;
|
|
19024
19334
|
} finally {
|
|
19025
|
-
|
|
19335
|
+
rmSync17(stagingRoot, { recursive: true, force: true });
|
|
19026
19336
|
}
|
|
19027
19337
|
}
|
|
19028
19338
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -19031,14 +19341,14 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
19031
19341
|
}
|
|
19032
19342
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
19033
19343
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
19034
|
-
stagingParentDir:
|
|
19344
|
+
stagingParentDir: dirname17(planV2Dir)
|
|
19035
19345
|
});
|
|
19036
19346
|
return resultFromManifest(planV2Dir, manifest);
|
|
19037
19347
|
}
|
|
19038
19348
|
function resultFromManifest(planV2Dir, manifest) {
|
|
19039
19349
|
return {
|
|
19040
19350
|
planDir: planV2Dir,
|
|
19041
|
-
manifestPath:
|
|
19351
|
+
manifestPath: join41(planV2Dir, "plan.json"),
|
|
19042
19352
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
19043
19353
|
planHash: manifest.planHash,
|
|
19044
19354
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -19174,7 +19484,7 @@ function parsePlanV2Manifest(value) {
|
|
|
19174
19484
|
return parsed;
|
|
19175
19485
|
}
|
|
19176
19486
|
function readPlanV2Manifest(planV2Dir) {
|
|
19177
|
-
const manifestPath =
|
|
19487
|
+
const manifestPath = join41(planV2Dir, "plan.json");
|
|
19178
19488
|
if (!existsSync26(manifestPath)) {
|
|
19179
19489
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
19180
19490
|
}
|
|
@@ -19197,7 +19507,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
19197
19507
|
if (!existsSync26(sourcePath)) {
|
|
19198
19508
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
19199
19509
|
}
|
|
19200
|
-
const stats =
|
|
19510
|
+
const stats = statSync12(sourcePath);
|
|
19201
19511
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
19202
19512
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
19203
19513
|
}
|
|
@@ -19219,17 +19529,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19219
19529
|
artifact,
|
|
19220
19530
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
19221
19531
|
}));
|
|
19222
|
-
|
|
19223
|
-
const tempDir = mkdtempSync7(
|
|
19532
|
+
mkdirSync17(dirname17(destinationDir), { recursive: true });
|
|
19533
|
+
const tempDir = mkdtempSync7(join41(dirname17(destinationDir), ".plan-v2-materialize-"));
|
|
19224
19534
|
try {
|
|
19225
19535
|
for (const { artifact, sourcePath } of verified) {
|
|
19226
|
-
const destinationPath =
|
|
19227
|
-
|
|
19228
|
-
|
|
19536
|
+
const destinationPath = join41(tempDir, ...artifact.path.split("/"));
|
|
19537
|
+
mkdirSync17(dirname17(destinationPath), { recursive: true });
|
|
19538
|
+
copyFileSync8(sourcePath, destinationPath);
|
|
19229
19539
|
}
|
|
19230
19540
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
19231
|
-
|
|
19232
|
-
|
|
19541
|
+
writeFileSync12(
|
|
19542
|
+
join41(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
19233
19543
|
canonicalJsonStringify({
|
|
19234
19544
|
manifest,
|
|
19235
19545
|
target,
|
|
@@ -19237,9 +19547,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19237
19547
|
}),
|
|
19238
19548
|
"utf-8"
|
|
19239
19549
|
);
|
|
19240
|
-
|
|
19550
|
+
renameWithRetry9(tempDir, destinationDir);
|
|
19241
19551
|
} catch (error) {
|
|
19242
|
-
|
|
19552
|
+
rmSync17(tempDir, { recursive: true, force: true });
|
|
19243
19553
|
throw error;
|
|
19244
19554
|
}
|
|
19245
19555
|
return {
|
|
@@ -19252,7 +19562,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19252
19562
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
19253
19563
|
// before the container change carries the legacy name, and materializing it
|
|
19254
19564
|
// under the new one would point at a file that was never written.
|
|
19255
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
19565
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join41(destinationDir, audioArtifact.path) : null : null
|
|
19256
19566
|
};
|
|
19257
19567
|
}
|
|
19258
19568
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -19271,7 +19581,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19271
19581
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
19272
19582
|
}
|
|
19273
19583
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19274
|
-
const markerPath =
|
|
19584
|
+
const markerPath = join41(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19275
19585
|
if (!existsSync26(markerPath)) return null;
|
|
19276
19586
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19277
19587
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -19289,8 +19599,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19289
19599
|
assertRemoteSources(remoteSources, required);
|
|
19290
19600
|
for (const artifact of required) {
|
|
19291
19601
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19292
|
-
const path =
|
|
19293
|
-
if (!existsSync26(path) ||
|
|
19602
|
+
const path = join41(planDir, ...artifact.path.split("/"));
|
|
19603
|
+
if (!existsSync26(path) || statSync12(path).size !== artifact.sizeBytes) {
|
|
19294
19604
|
throw new PlanV2IntegrityError(
|
|
19295
19605
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19296
19606
|
);
|
|
@@ -19303,23 +19613,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19303
19613
|
}
|
|
19304
19614
|
|
|
19305
19615
|
// src/services/distributed/planV2Execution.ts
|
|
19306
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19616
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync20 } from "node:fs";
|
|
19307
19617
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
19308
|
-
import { join as
|
|
19618
|
+
import { join as join44 } from "node:path";
|
|
19309
19619
|
|
|
19310
19620
|
// src/services/distributed/assemble.ts
|
|
19311
19621
|
import {
|
|
19312
19622
|
cpSync as cpSync3,
|
|
19313
19623
|
existsSync as existsSync27,
|
|
19314
|
-
mkdirSync as
|
|
19624
|
+
mkdirSync as mkdirSync18,
|
|
19315
19625
|
mkdtempSync as mkdtempSync8,
|
|
19316
|
-
readFileSync as
|
|
19626
|
+
readFileSync as readFileSync15,
|
|
19317
19627
|
readdirSync as readdirSync10,
|
|
19318
|
-
rmSync as
|
|
19319
|
-
statSync as
|
|
19320
|
-
writeFileSync as
|
|
19628
|
+
rmSync as rmSync18,
|
|
19629
|
+
statSync as statSync13,
|
|
19630
|
+
writeFileSync as writeFileSync13
|
|
19321
19631
|
} from "node:fs";
|
|
19322
|
-
import { dirname as
|
|
19632
|
+
import { dirname as dirname18, join as join42 } from "node:path";
|
|
19323
19633
|
import {
|
|
19324
19634
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
19325
19635
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -19327,9 +19637,9 @@ import {
|
|
|
19327
19637
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
19328
19638
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
19329
19639
|
resolveConfig as resolveConfig6,
|
|
19330
|
-
runFfmpeg as
|
|
19640
|
+
runFfmpeg as runFfmpeg8,
|
|
19331
19641
|
appendSoftwareEncoderThreadsArg,
|
|
19332
|
-
renameWithRetry as
|
|
19642
|
+
renameWithRetry as renameWithRetry10
|
|
19333
19643
|
} from "@hyperframes/engine";
|
|
19334
19644
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
19335
19645
|
function cfrRateControlArgs(locked) {
|
|
@@ -19355,13 +19665,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19355
19665
|
outputPath,
|
|
19356
19666
|
durationMs: Date.now() - start,
|
|
19357
19667
|
framesEncoded: plan2.totalFrames,
|
|
19358
|
-
fileSize:
|
|
19668
|
+
fileSize: statSync13(outputPath).size
|
|
19359
19669
|
};
|
|
19360
19670
|
}
|
|
19361
19671
|
const format = plan2.dimensions.format;
|
|
19362
19672
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
19363
|
-
if (!existsSync27(
|
|
19364
|
-
|
|
19673
|
+
if (!existsSync27(dirname18(outputPath))) {
|
|
19674
|
+
mkdirSync18(dirname18(outputPath), { recursive: true });
|
|
19365
19675
|
}
|
|
19366
19676
|
const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
|
|
19367
19677
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -19383,10 +19693,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19383
19693
|
};
|
|
19384
19694
|
try {
|
|
19385
19695
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
19386
|
-
const finalPath =
|
|
19696
|
+
const finalPath = join42(workDir, `output.${format}`);
|
|
19387
19697
|
const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
|
|
19388
19698
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
19389
|
-
|
|
19699
|
+
renameWithRetry10(finalPath, outputPath);
|
|
19390
19700
|
log.info("[assemble] stage timings", {
|
|
19391
19701
|
...stageMs,
|
|
19392
19702
|
chunks: chunkPaths.length,
|
|
@@ -19394,7 +19704,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19394
19704
|
});
|
|
19395
19705
|
} finally {
|
|
19396
19706
|
try {
|
|
19397
|
-
|
|
19707
|
+
rmSync18(workDir, { recursive: true, force: true });
|
|
19398
19708
|
} catch (err) {
|
|
19399
19709
|
log.warn("[assemble] failed to remove work dir", {
|
|
19400
19710
|
workDir,
|
|
@@ -19402,7 +19712,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19402
19712
|
});
|
|
19403
19713
|
}
|
|
19404
19714
|
}
|
|
19405
|
-
const fileSize = existsSync27(outputPath) ?
|
|
19715
|
+
const fileSize = existsSync27(outputPath) ? statSync13(outputPath).size : 0;
|
|
19406
19716
|
return {
|
|
19407
19717
|
outputPath,
|
|
19408
19718
|
durationMs: Date.now() - start,
|
|
@@ -19411,18 +19721,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19411
19721
|
};
|
|
19412
19722
|
}
|
|
19413
19723
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
19414
|
-
const planJsonPath =
|
|
19415
|
-
const chunksJsonPath =
|
|
19724
|
+
const planJsonPath = join42(planDir, "plan.json");
|
|
19725
|
+
const chunksJsonPath = join42(planDir, "meta", "chunks.json");
|
|
19416
19726
|
if (!existsSync27(planJsonPath)) {
|
|
19417
19727
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
19418
19728
|
}
|
|
19419
|
-
const plan2 = JSON.parse(
|
|
19729
|
+
const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
|
|
19420
19730
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
19421
19731
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
19422
19732
|
if (!existsSync27(chunksJsonPath)) {
|
|
19423
19733
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
19424
19734
|
}
|
|
19425
|
-
const chunks = JSON.parse(
|
|
19735
|
+
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
19426
19736
|
if (chunkPaths.length !== chunks.length) {
|
|
19427
19737
|
throw new Error(
|
|
19428
19738
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -19460,7 +19770,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
19460
19770
|
return args;
|
|
19461
19771
|
}
|
|
19462
19772
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
19463
|
-
const cfrOutputPath =
|
|
19773
|
+
const cfrOutputPath = join42(ctx.workDir, `cfr.${ctx.format}`);
|
|
19464
19774
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
19465
19775
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
19466
19776
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -19487,7 +19797,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19487
19797
|
);
|
|
19488
19798
|
return;
|
|
19489
19799
|
}
|
|
19490
|
-
const paddedAudioPath =
|
|
19800
|
+
const paddedAudioPath = join42(ctx.workDir, "audio-padded.m4a");
|
|
19491
19801
|
const padTrimResult = await ctx.timed(
|
|
19492
19802
|
"audioMs",
|
|
19493
19803
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -19527,9 +19837,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
19527
19837
|
}
|
|
19528
19838
|
}
|
|
19529
19839
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
19530
|
-
const listPath =
|
|
19840
|
+
const listPath = join42(workDir, "concat-list.txt");
|
|
19531
19841
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
19532
|
-
|
|
19842
|
+
writeFileSync13(listPath, `${body}
|
|
19533
19843
|
`, "utf-8");
|
|
19534
19844
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
19535
19845
|
}
|
|
@@ -19544,7 +19854,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
|
|
|
19544
19854
|
};
|
|
19545
19855
|
}
|
|
19546
19856
|
async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
19547
|
-
const result = await
|
|
19857
|
+
const result = await runFfmpeg8(args, { signal, timeout });
|
|
19548
19858
|
if (!result.success) {
|
|
19549
19859
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
19550
19860
|
error: describeFfmpegFailure4(result, timeout),
|
|
@@ -19565,13 +19875,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
19565
19875
|
return lockedAudio(planDir).separateAudio === true;
|
|
19566
19876
|
}
|
|
19567
19877
|
function planFormat(planDir) {
|
|
19568
|
-
const plan2 = JSON.parse(
|
|
19878
|
+
const plan2 = JSON.parse(readFileSync15(join42(planDir, "plan.json"), "utf-8"));
|
|
19569
19879
|
return plan2.dimensions.format;
|
|
19570
19880
|
}
|
|
19571
19881
|
function lockedAudio(planDir) {
|
|
19572
|
-
const encoderJsonPath =
|
|
19882
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19573
19883
|
if (!existsSync27(encoderJsonPath)) return {};
|
|
19574
|
-
return JSON.parse(
|
|
19884
|
+
return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19575
19885
|
}
|
|
19576
19886
|
function readCfrEncoder(planDir, format) {
|
|
19577
19887
|
if (format !== "mp4") {
|
|
@@ -19579,11 +19889,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19579
19889
|
`[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.`
|
|
19580
19890
|
);
|
|
19581
19891
|
}
|
|
19582
|
-
const encoderJsonPath =
|
|
19892
|
+
const encoderJsonPath = join42(planDir, "meta", "encoder.json");
|
|
19583
19893
|
if (!existsSync27(encoderJsonPath)) {
|
|
19584
19894
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
19585
19895
|
}
|
|
19586
|
-
const encoderJson = JSON.parse(
|
|
19896
|
+
const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
19587
19897
|
if (encoderJson.encoder === "libx265-software") {
|
|
19588
19898
|
throw new Error(
|
|
19589
19899
|
`[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".`
|
|
@@ -19592,11 +19902,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
19592
19902
|
return encoderJson;
|
|
19593
19903
|
}
|
|
19594
19904
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19595
|
-
if (existsSync27(outputPath))
|
|
19596
|
-
|
|
19905
|
+
if (existsSync27(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
19906
|
+
mkdirSync18(outputPath, { recursive: true });
|
|
19597
19907
|
let globalIdx = 0;
|
|
19598
19908
|
for (const chunkDir of chunkPaths) {
|
|
19599
|
-
if (!
|
|
19909
|
+
if (!statSync13(chunkDir).isDirectory()) {
|
|
19600
19910
|
throw new Error(
|
|
19601
19911
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
19602
19912
|
);
|
|
@@ -19606,8 +19916,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19606
19916
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
19607
19917
|
}
|
|
19608
19918
|
for (const frame of frames) {
|
|
19609
|
-
const dst =
|
|
19610
|
-
cpSync3(
|
|
19919
|
+
const dst = join42(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
19920
|
+
cpSync3(join42(chunkDir, frame), dst);
|
|
19611
19921
|
globalIdx += 1;
|
|
19612
19922
|
}
|
|
19613
19923
|
}
|
|
@@ -19617,13 +19927,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19617
19927
|
);
|
|
19618
19928
|
}
|
|
19619
19929
|
if (audioPath !== null && existsSync27(audioPath)) {
|
|
19620
|
-
const sidecar =
|
|
19930
|
+
const sidecar = join42(outputPath, MIXED_AUDIO_FILENAME4);
|
|
19621
19931
|
cpSync3(audioPath, sidecar);
|
|
19622
19932
|
}
|
|
19623
19933
|
let fileSize = 0;
|
|
19624
19934
|
for (const name of readdirSync10(outputPath)) {
|
|
19625
19935
|
try {
|
|
19626
|
-
fileSize +=
|
|
19936
|
+
fileSize += statSync13(join42(outputPath, name)).size;
|
|
19627
19937
|
} catch {
|
|
19628
19938
|
}
|
|
19629
19939
|
}
|
|
@@ -19637,9 +19947,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19637
19947
|
|
|
19638
19948
|
// src/services/distributed/renderChunk.ts
|
|
19639
19949
|
import { randomBytes } from "node:crypto";
|
|
19640
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
19950
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as rmSync19, writeFileSync as writeFileSync14 } from "node:fs";
|
|
19641
19951
|
import { cpus as cpus3 } from "node:os";
|
|
19642
|
-
import { join as
|
|
19952
|
+
import { join as join43 } from "node:path";
|
|
19643
19953
|
import {
|
|
19644
19954
|
assertSwiftShader,
|
|
19645
19955
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -19758,10 +20068,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
19758
20068
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
19759
20069
|
}
|
|
19760
20070
|
function hashChunkOutput(outputPath, kind) {
|
|
19761
|
-
if (kind === "file") return sha256Hex(
|
|
20071
|
+
if (kind === "file") return sha256Hex(readFileSync16(outputPath));
|
|
19762
20072
|
const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19763
20073
|
const lines = entries.map(
|
|
19764
|
-
(name) => `${name}\0${sha256Hex(
|
|
20074
|
+
(name) => `${name}\0${sha256Hex(readFileSync16(join43(outputPath, name)))}`
|
|
19765
20075
|
);
|
|
19766
20076
|
return sha256Hex(lines.join("\0"));
|
|
19767
20077
|
}
|
|
@@ -19778,16 +20088,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
19778
20088
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
19779
20089
|
const start = Date.now();
|
|
19780
20090
|
const log = defaultLogger;
|
|
19781
|
-
const planJsonPath =
|
|
19782
|
-
const encoderJsonPath =
|
|
19783
|
-
const chunksJsonPath =
|
|
20091
|
+
const planJsonPath = join43(planDir, "plan.json");
|
|
20092
|
+
const encoderJsonPath = join43(planDir, "meta", "encoder.json");
|
|
20093
|
+
const chunksJsonPath = join43(planDir, "meta", "chunks.json");
|
|
19784
20094
|
if (!existsSync28(planJsonPath)) {
|
|
19785
20095
|
throw new RenderChunkValidationError(
|
|
19786
20096
|
MISSING_PLAN_ARTIFACT,
|
|
19787
20097
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
19788
20098
|
);
|
|
19789
20099
|
}
|
|
19790
|
-
const plan2 = JSON.parse(
|
|
20100
|
+
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
19791
20101
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
19792
20102
|
const planHashStarted = Date.now();
|
|
19793
20103
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -19804,13 +20114,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19804
20114
|
);
|
|
19805
20115
|
}
|
|
19806
20116
|
}
|
|
19807
|
-
const encoder = JSON.parse(
|
|
19808
|
-
const chunks = JSON.parse(
|
|
19809
|
-
const videosJsonPath =
|
|
20117
|
+
const encoder = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
20118
|
+
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
20119
|
+
const videosJsonPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19810
20120
|
let planVideos = null;
|
|
19811
20121
|
if (existsSync28(videosJsonPath)) {
|
|
19812
20122
|
try {
|
|
19813
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
20123
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync16(videosJsonPath, "utf-8")));
|
|
19814
20124
|
} catch (err) {
|
|
19815
20125
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
19816
20126
|
throw new RenderChunkValidationError(
|
|
@@ -19838,7 +20148,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19838
20148
|
}
|
|
19839
20149
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19840
20150
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19841
|
-
const compiledDir =
|
|
20151
|
+
const compiledDir = join43(planDir, "compiled");
|
|
19842
20152
|
if (!existsSync28(compiledDir)) {
|
|
19843
20153
|
throw new RenderChunkValidationError(
|
|
19844
20154
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -19895,9 +20205,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19895
20205
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
19896
20206
|
};
|
|
19897
20207
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19898
|
-
|
|
19899
|
-
const framesDir =
|
|
19900
|
-
|
|
20208
|
+
mkdirSync19(workDir, { recursive: true });
|
|
20209
|
+
const framesDir = join43(workDir, "captured-frames");
|
|
20210
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
19901
20211
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19902
20212
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
19903
20213
|
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -19921,7 +20231,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19921
20231
|
dimensions: plan2.dimensions,
|
|
19922
20232
|
encoder,
|
|
19923
20233
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19924
|
-
html:
|
|
20234
|
+
html: readFileSync16(join43(compiledDir, "index.html"), "utf-8"),
|
|
19925
20235
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19926
20236
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19927
20237
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -20050,10 +20360,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20050
20360
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
20051
20361
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
20052
20362
|
if (isPngSequence) {
|
|
20053
|
-
if (!existsSync28(outputChunkPath))
|
|
20363
|
+
if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
|
|
20054
20364
|
} else {
|
|
20055
|
-
const outDir =
|
|
20056
|
-
if (!existsSync28(outDir))
|
|
20365
|
+
const outDir = join43(outputChunkPath, "..");
|
|
20366
|
+
if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
|
|
20057
20367
|
}
|
|
20058
20368
|
const encodeInput = {
|
|
20059
20369
|
job,
|
|
@@ -20164,8 +20474,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20164
20474
|
return observedChunkCaptureMode(capturePerfs);
|
|
20165
20475
|
},
|
|
20166
20476
|
resetForScreenshotRetry: () => {
|
|
20167
|
-
|
|
20168
|
-
|
|
20477
|
+
rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20478
|
+
mkdirSync19(framesDir, { recursive: true });
|
|
20169
20479
|
capturePerfs.length = 0;
|
|
20170
20480
|
captureAttempts.length = 0;
|
|
20171
20481
|
job.framesRendered = 0;
|
|
@@ -20207,8 +20517,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20207
20517
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
20208
20518
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
20209
20519
|
});
|
|
20210
|
-
|
|
20211
|
-
|
|
20520
|
+
rmSync19(videoOnlyPath, { force: true });
|
|
20521
|
+
rmSync19(audioScratchPath, { force: true });
|
|
20212
20522
|
}
|
|
20213
20523
|
encodeStageMs = Date.now() - encodeStarted;
|
|
20214
20524
|
} finally {
|
|
@@ -20255,11 +20565,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20255
20565
|
producerVersion: plan2.producerVersion,
|
|
20256
20566
|
ffmpegVersion
|
|
20257
20567
|
};
|
|
20258
|
-
|
|
20568
|
+
writeFileSync14(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
20259
20569
|
`, "utf-8");
|
|
20260
20570
|
try {
|
|
20261
|
-
|
|
20262
|
-
|
|
20571
|
+
rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20572
|
+
rmSync19(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20263
20573
|
} catch (err) {
|
|
20264
20574
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
20265
20575
|
workDir,
|
|
@@ -20288,18 +20598,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20288
20598
|
|
|
20289
20599
|
// src/services/distributed/planV2Execution.ts
|
|
20290
20600
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
20291
|
-
const workRoot = mkdtempSync9(
|
|
20292
|
-
const materializedPlanDir =
|
|
20601
|
+
const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-chunk-"));
|
|
20602
|
+
const materializedPlanDir = join44(workRoot, "plan");
|
|
20293
20603
|
try {
|
|
20294
20604
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
20295
20605
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
20296
20606
|
} finally {
|
|
20297
|
-
|
|
20607
|
+
rmSync20(workRoot, { recursive: true, force: true });
|
|
20298
20608
|
}
|
|
20299
20609
|
}
|
|
20300
20610
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
20301
|
-
const workRoot = mkdtempSync9(
|
|
20302
|
-
const materializedPlanDir =
|
|
20611
|
+
const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-assembler-"));
|
|
20612
|
+
const materializedPlanDir = join44(workRoot, "plan");
|
|
20303
20613
|
try {
|
|
20304
20614
|
const materialized = materializePlanV2Target(
|
|
20305
20615
|
planV2Dir,
|
|
@@ -20314,7 +20624,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
20314
20624
|
options
|
|
20315
20625
|
);
|
|
20316
20626
|
} finally {
|
|
20317
|
-
|
|
20627
|
+
rmSync20(workRoot, { recursive: true, force: true });
|
|
20318
20628
|
}
|
|
20319
20629
|
}
|
|
20320
20630
|
|
|
@@ -20322,7 +20632,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
20322
20632
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
20323
20633
|
|
|
20324
20634
|
// src/services/distributed/audioSidecar.ts
|
|
20325
|
-
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as
|
|
20635
|
+
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
|
|
20326
20636
|
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
20327
20637
|
|
|
20328
20638
|
// src/services/distributed/probeFacts.ts
|
|
@@ -20386,6 +20696,7 @@ export {
|
|
|
20386
20696
|
localizeRemoteImageSources,
|
|
20387
20697
|
localizeRemoteMediaSources,
|
|
20388
20698
|
materializePlanV2Target,
|
|
20699
|
+
mediaProfileMatchesElementType,
|
|
20389
20700
|
normalizeErrorMessage,
|
|
20390
20701
|
parseRenderRequest,
|
|
20391
20702
|
plan,
|
|
@@ -20393,6 +20704,7 @@ export {
|
|
|
20393
20704
|
planV2WithPublisher,
|
|
20394
20705
|
prepareAnimatedGifInputs,
|
|
20395
20706
|
prepareCaptureSessionForReuse4 as prepareCaptureSessionForReuse,
|
|
20707
|
+
prepareHeifImageInputs,
|
|
20396
20708
|
prepareHyperframeLintBody,
|
|
20397
20709
|
publishPlanV2FromExecutionPlan,
|
|
20398
20710
|
publishPlanV2FromV1,
|