@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/distributed.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/services/distributed/plan.ts
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
|
-
copyFileSync as
|
|
4
|
+
copyFileSync as copyFileSync6,
|
|
5
5
|
cpSync as cpSync2,
|
|
6
6
|
existsSync as existsSync15,
|
|
7
7
|
linkSync,
|
|
8
|
-
mkdirSync as
|
|
9
|
-
rmSync as
|
|
10
|
-
writeFileSync as
|
|
8
|
+
mkdirSync as mkdirSync10,
|
|
9
|
+
rmSync as rmSync9,
|
|
10
|
+
writeFileSync as writeFileSync7
|
|
11
11
|
} from "node:fs";
|
|
12
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as
|
|
12
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join22, relative as relative5, sep as sep2 } from "node:path";
|
|
13
13
|
import {
|
|
14
14
|
fpsToNumber as fpsToNumber10,
|
|
15
15
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
normalizeVp9CpuUsed,
|
|
20
20
|
resolveConfig as resolveConfig3,
|
|
21
21
|
declareContentAddressedUrl,
|
|
22
|
-
renameWithRetry as
|
|
22
|
+
renameWithRetry as renameWithRetry7,
|
|
23
23
|
declareProbeFactsUrl
|
|
24
24
|
} from "@hyperframes/engine";
|
|
25
25
|
|
|
@@ -66,13 +66,13 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
66
66
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
67
67
|
import {
|
|
68
68
|
existsSync as existsSync10,
|
|
69
|
-
mkdirSync as
|
|
70
|
-
readFileSync as
|
|
69
|
+
mkdirSync as mkdirSync6,
|
|
70
|
+
readFileSync as readFileSync6,
|
|
71
71
|
readdirSync as readdirSync3,
|
|
72
|
-
rmSync as
|
|
73
|
-
statSync as
|
|
74
|
-
writeFileSync as
|
|
75
|
-
copyFileSync as
|
|
72
|
+
rmSync as rmSync7,
|
|
73
|
+
statSync as statSync5,
|
|
74
|
+
writeFileSync as writeFileSync4,
|
|
75
|
+
copyFileSync as copyFileSync5,
|
|
76
76
|
appendFileSync
|
|
77
77
|
} from "fs";
|
|
78
78
|
import {
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
augmentProtocolTimeoutError,
|
|
120
120
|
augmentPageNavigationTimeoutError
|
|
121
121
|
} from "@hyperframes/engine";
|
|
122
|
-
import { join as
|
|
122
|
+
import { join as join14, dirname as dirname8, resolve as resolve7 } from "path";
|
|
123
123
|
import { randomUUID } from "crypto";
|
|
124
124
|
|
|
125
125
|
// src/services/fileServer.ts
|
|
@@ -141,6 +141,7 @@ import { Readable } from "node:stream";
|
|
|
141
141
|
import { join, extname, resolve as resolve2, sep } from "node:path";
|
|
142
142
|
import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
|
|
143
143
|
import { fpsToNumber } from "@hyperframes/core";
|
|
144
|
+
import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
|
|
144
145
|
|
|
145
146
|
// src/services/hyperframeRuntimeLoader.ts
|
|
146
147
|
import { createHash } from "node:crypto";
|
|
@@ -218,11 +219,11 @@ function getHfEarlyStub() {
|
|
|
218
219
|
|
|
219
220
|
// src/services/fileServer.ts
|
|
220
221
|
var readFileAsync = promisify(readFile);
|
|
221
|
-
function isPathInside(
|
|
222
|
+
function isPathInside(child3, parent, options = {}) {
|
|
222
223
|
const { resolveSymlinks = false, pathModule } = options;
|
|
223
224
|
const resolveFn = pathModule?.resolve ?? resolve2;
|
|
224
225
|
const separator = pathModule?.sep ?? sep;
|
|
225
|
-
const resolvedChild = resolveFn(
|
|
226
|
+
const resolvedChild = resolveFn(child3);
|
|
226
227
|
const resolvedParent = resolveFn(parent);
|
|
227
228
|
const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
|
|
228
229
|
const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
|
|
@@ -738,6 +739,18 @@ function createFileServer(options) {
|
|
|
738
739
|
html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
739
740
|
return c.text(html, 200, { "Content-Type": contentType });
|
|
740
741
|
}
|
|
742
|
+
if (ext === ".png") {
|
|
743
|
+
const untagged = stripSdrPngColourTags(await readFileAsync(fd));
|
|
744
|
+
if (untagged) {
|
|
745
|
+
const headers = {
|
|
746
|
+
"Content-Type": contentType,
|
|
747
|
+
"Content-Length": String(untagged.length)
|
|
748
|
+
};
|
|
749
|
+
return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
|
|
750
|
+
headers
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
741
754
|
const totalSize = stat.size;
|
|
742
755
|
const rangeHeader = c.req.header("range");
|
|
743
756
|
const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
|
|
@@ -765,7 +778,8 @@ function createFileServer(options) {
|
|
|
765
778
|
const stream = createReadStream(filePath, {
|
|
766
779
|
fd,
|
|
767
780
|
autoClose: true,
|
|
768
|
-
|
|
781
|
+
start: range?.start ?? 0,
|
|
782
|
+
...range ? { end: range.end } : {}
|
|
769
783
|
});
|
|
770
784
|
streamOwnsFd = true;
|
|
771
785
|
try {
|
|
@@ -779,10 +793,10 @@ function createFileServer(options) {
|
|
|
779
793
|
if (!streamOwnsFd) closeSync(fd);
|
|
780
794
|
}
|
|
781
795
|
});
|
|
782
|
-
return new Promise((
|
|
796
|
+
return new Promise((resolve10) => {
|
|
783
797
|
const connections = /* @__PURE__ */ new Set();
|
|
784
798
|
const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
|
|
785
|
-
|
|
799
|
+
resolve10({
|
|
786
800
|
url: `http://localhost:${info.port}`,
|
|
787
801
|
port: info.port,
|
|
788
802
|
addPreHeadScript: (script) => {
|
|
@@ -1782,15 +1796,15 @@ import {
|
|
|
1782
1796
|
} from "@hyperframes/engine";
|
|
1783
1797
|
|
|
1784
1798
|
// src/services/render/stages/compileStage.ts
|
|
1785
|
-
import { join as
|
|
1799
|
+
import { join as join7 } from "node:path";
|
|
1786
1800
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1787
1801
|
|
|
1788
1802
|
// src/services/htmlCompiler.ts
|
|
1789
1803
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1790
|
-
import { createHash as
|
|
1791
|
-
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as
|
|
1792
|
-
import { join as
|
|
1793
|
-
import { parseHTML as
|
|
1804
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1805
|
+
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1806
|
+
import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1807
|
+
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1794
1808
|
import {
|
|
1795
1809
|
COMPOSITION_SOURCE_URL,
|
|
1796
1810
|
compileTimingAttrs,
|
|
@@ -2066,7 +2080,7 @@ function readLoopOverride(el) {
|
|
|
2066
2080
|
}
|
|
2067
2081
|
return null;
|
|
2068
2082
|
}
|
|
2069
|
-
function
|
|
2083
|
+
function resolveLocalImageSourcePath(src, options) {
|
|
2070
2084
|
const trimmed = src.trim();
|
|
2071
2085
|
if (!trimmed || trimmed.startsWith("data:")) return null;
|
|
2072
2086
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
@@ -2289,7 +2303,7 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2289
2303
|
const img = images[i];
|
|
2290
2304
|
const src = (img.getAttribute("src") || "").trim();
|
|
2291
2305
|
if (!hasGifExtension(src)) continue;
|
|
2292
|
-
const sourcePath =
|
|
2306
|
+
const sourcePath = resolveLocalImageSourcePath(src, options);
|
|
2293
2307
|
if (!sourcePath) continue;
|
|
2294
2308
|
const bytes = readFileSync2(sourcePath);
|
|
2295
2309
|
const metadata = parseAnimatedGifMetadata(bytes);
|
|
@@ -2337,19 +2351,74 @@ async function prepareAnimatedGifInputs(html, options) {
|
|
|
2337
2351
|
};
|
|
2338
2352
|
}
|
|
2339
2353
|
|
|
2340
|
-
// src/services/
|
|
2341
|
-
import {
|
|
2342
|
-
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2343
|
-
|
|
2344
|
-
// src/services/assetMediaType.ts
|
|
2345
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
2354
|
+
// src/services/heifImagePrep.ts
|
|
2355
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2346
2356
|
import {
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2357
|
+
closeSync as closeSync2,
|
|
2358
|
+
copyFileSync as copyFileSync3,
|
|
2359
|
+
mkdirSync as mkdirSync3,
|
|
2360
|
+
openSync as openSync2,
|
|
2361
|
+
readFileSync as readFileSync3,
|
|
2362
|
+
readSync,
|
|
2363
|
+
rmSync as rmSync3,
|
|
2364
|
+
statSync as statSync3,
|
|
2365
|
+
writeFileSync as writeFileSync2
|
|
2366
|
+
} from "node:fs";
|
|
2367
|
+
import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
|
|
2368
|
+
import { parseHTML as parseHTML4 } from "linkedom";
|
|
2369
|
+
import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
|
|
2370
|
+
|
|
2371
|
+
// src/services/heifRotation.ts
|
|
2372
|
+
function* boxes(buf, start, end) {
|
|
2373
|
+
let at = start;
|
|
2374
|
+
while (at + 8 <= end) {
|
|
2375
|
+
let size = buf.readUInt32BE(at);
|
|
2376
|
+
let header = 8;
|
|
2377
|
+
if (size === 1 && at + 16 <= end) {
|
|
2378
|
+
size = Number(buf.readBigUInt64BE(at + 8));
|
|
2379
|
+
header = 16;
|
|
2380
|
+
} else if (size === 0) {
|
|
2381
|
+
size = end - at;
|
|
2382
|
+
}
|
|
2383
|
+
if (size < header || at + size > end) return;
|
|
2384
|
+
yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
|
|
2385
|
+
at += size;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
function child(buf, parent, type, skip = 0) {
|
|
2389
|
+
return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
|
|
2390
|
+
}
|
|
2391
|
+
function readHeifPrimaryRotation(buf) {
|
|
2392
|
+
try {
|
|
2393
|
+
const root = { type: "", start: 0, end: buf.length };
|
|
2394
|
+
const meta = child(buf, root, "meta")[0];
|
|
2395
|
+
const pitm = meta && child(buf, meta, "pitm", 4)[0];
|
|
2396
|
+
const iprp = meta && child(buf, meta, "iprp", 4)[0];
|
|
2397
|
+
const ipco = iprp && child(buf, iprp, "ipco")[0];
|
|
2398
|
+
if (!pitm || !ipco || !iprp) return 0;
|
|
2399
|
+
const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
|
|
2400
|
+
const props = [...boxes(buf, ipco.start, ipco.end)];
|
|
2401
|
+
for (const ipma of child(buf, iprp, "ipma")) {
|
|
2402
|
+
const version = buf[ipma.start];
|
|
2403
|
+
const wide = (buf[ipma.start + 3] & 1) === 1;
|
|
2404
|
+
let at = ipma.start + 8;
|
|
2405
|
+
for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
|
|
2406
|
+
const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
|
|
2407
|
+
at += version < 1 ? 2 : 4;
|
|
2408
|
+
const count = buf[at++];
|
|
2409
|
+
for (let i = 0; i < count; i++) {
|
|
2410
|
+
const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
|
|
2411
|
+
at += wide ? 2 : 1;
|
|
2412
|
+
const prop = props[index - 1];
|
|
2413
|
+
if (id === primaryId && prop?.type === "irot")
|
|
2414
|
+
return (buf[prop.start] & 3) * 90;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
} catch {
|
|
2419
|
+
}
|
|
2420
|
+
return 0;
|
|
2421
|
+
}
|
|
2353
2422
|
|
|
2354
2423
|
// src/utils/semaphore.ts
|
|
2355
2424
|
var Semaphore = class {
|
|
@@ -2364,10 +2433,10 @@ var Semaphore = class {
|
|
|
2364
2433
|
this.active++;
|
|
2365
2434
|
return () => this.release();
|
|
2366
2435
|
}
|
|
2367
|
-
return new Promise((
|
|
2436
|
+
return new Promise((resolve10) => {
|
|
2368
2437
|
this.queue.push(() => {
|
|
2369
2438
|
this.active++;
|
|
2370
|
-
|
|
2439
|
+
resolve10(() => this.release());
|
|
2371
2440
|
});
|
|
2372
2441
|
});
|
|
2373
2442
|
}
|
|
@@ -2398,7 +2467,229 @@ async function withMediaProbeSlot(operation) {
|
|
|
2398
2467
|
}
|
|
2399
2468
|
}
|
|
2400
2469
|
|
|
2470
|
+
// src/services/heifImagePrep.ts
|
|
2471
|
+
var PREPARED_HEIF_SUBDIR = "_heif_images";
|
|
2472
|
+
var CACHE_SCHEMA2 = "hfheif-v1";
|
|
2473
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2474
|
+
var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
|
|
2475
|
+
var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
|
|
2476
|
+
var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
|
|
2477
|
+
var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
2478
|
+
var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
|
|
2479
|
+
var HeifImageConversionError = class extends Error {
|
|
2480
|
+
code = HEIF_IMAGE_CONVERSION_FAILED;
|
|
2481
|
+
owner = "user";
|
|
2482
|
+
retryable = false;
|
|
2483
|
+
constructor(label, reason) {
|
|
2484
|
+
super(
|
|
2485
|
+
`Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
|
|
2486
|
+
);
|
|
2487
|
+
this.name = "HeifImageConversionError";
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
var HeifToolUnavailableError = class extends Error {
|
|
2491
|
+
code = HEIF_TOOL_UNAVAILABLE;
|
|
2492
|
+
owner = "system";
|
|
2493
|
+
retryable = false;
|
|
2494
|
+
constructor(label, reason) {
|
|
2495
|
+
super(
|
|
2496
|
+
`Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
|
|
2497
|
+
);
|
|
2498
|
+
this.name = "HeifToolUnavailableError";
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
|
|
2502
|
+
function isHeifBytes(head) {
|
|
2503
|
+
const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
|
|
2504
|
+
if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
|
|
2505
|
+
const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
|
|
2506
|
+
if (boxEnd < 16) return false;
|
|
2507
|
+
const brands = [bytes.toString("ascii", 8, 12)];
|
|
2508
|
+
for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
|
|
2509
|
+
brands.push(bytes.toString("ascii", offset, offset + 4));
|
|
2510
|
+
}
|
|
2511
|
+
return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
|
|
2512
|
+
}
|
|
2513
|
+
function readHead(path) {
|
|
2514
|
+
let fd;
|
|
2515
|
+
try {
|
|
2516
|
+
fd = openSync2(path, "r");
|
|
2517
|
+
const buf = Buffer.alloc(4096);
|
|
2518
|
+
return buf.subarray(0, readSync(fd, buf, 0, buf.length, 0));
|
|
2519
|
+
} catch {
|
|
2520
|
+
return null;
|
|
2521
|
+
} finally {
|
|
2522
|
+
if (fd !== void 0) closeSync2(fd);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
async function probeJson(path, withGroups) {
|
|
2526
|
+
const stdout = await runFfprobe(
|
|
2527
|
+
path,
|
|
2528
|
+
withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
|
|
2529
|
+
);
|
|
2530
|
+
return JSON.parse(stdout);
|
|
2531
|
+
}
|
|
2532
|
+
var ROTATION_FILTER = {
|
|
2533
|
+
0: "",
|
|
2534
|
+
90: "transpose=2",
|
|
2535
|
+
180: "hflip,vflip",
|
|
2536
|
+
270: "transpose=1"
|
|
2537
|
+
};
|
|
2538
|
+
function primaryGrid(probed) {
|
|
2539
|
+
const groups = probed.stream_groups ?? [];
|
|
2540
|
+
return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
|
|
2541
|
+
}
|
|
2542
|
+
async function convertHeifToJpeg(request) {
|
|
2543
|
+
const probed = await probeJson(request.inputPath, true);
|
|
2544
|
+
const grid = primaryGrid(probed);
|
|
2545
|
+
const rotation = readHeifPrimaryRotation(readFileSync3(request.inputPath));
|
|
2546
|
+
const filters = [];
|
|
2547
|
+
let inputs = [];
|
|
2548
|
+
let size = probed.streams?.[0];
|
|
2549
|
+
if (grid?.subcomponents?.length) {
|
|
2550
|
+
size = grid;
|
|
2551
|
+
const layout = grid.subcomponents.map(
|
|
2552
|
+
(t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
|
|
2553
|
+
);
|
|
2554
|
+
inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
|
|
2555
|
+
filters.push(
|
|
2556
|
+
`${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
|
|
2557
|
+
`crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (!size?.width || !size.height) throw new Error("no declared image size");
|
|
2561
|
+
if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
|
|
2562
|
+
const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
|
|
2563
|
+
const result = await runFfmpeg3(
|
|
2564
|
+
[
|
|
2565
|
+
"-hide_banner",
|
|
2566
|
+
"-loglevel",
|
|
2567
|
+
"error",
|
|
2568
|
+
"-i",
|
|
2569
|
+
request.inputPath,
|
|
2570
|
+
...mapArgs,
|
|
2571
|
+
"-frames:v",
|
|
2572
|
+
"1",
|
|
2573
|
+
"-q:v",
|
|
2574
|
+
"2",
|
|
2575
|
+
"-f",
|
|
2576
|
+
"mjpeg",
|
|
2577
|
+
"-y",
|
|
2578
|
+
request.outputPath
|
|
2579
|
+
],
|
|
2580
|
+
{ timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
|
|
2581
|
+
);
|
|
2582
|
+
if (!result.success) {
|
|
2583
|
+
throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
|
|
2584
|
+
}
|
|
2585
|
+
const out = (await probeJson(request.outputPath, false)).streams?.[0];
|
|
2586
|
+
const same = out?.width === size.width && out.height === size.height;
|
|
2587
|
+
const swapped = out?.width === size.height && out.height === size.width;
|
|
2588
|
+
if (!same && !swapped) {
|
|
2589
|
+
throw new Error(
|
|
2590
|
+
`wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
function resolveImageSource(src, options) {
|
|
2595
|
+
const data = DATA_URL_RE.exec(src);
|
|
2596
|
+
if (data) {
|
|
2597
|
+
const bytes = Buffer.from(data[1], "base64");
|
|
2598
|
+
return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
|
|
2599
|
+
}
|
|
2600
|
+
const path = resolveLocalImageSourcePath(src, options);
|
|
2601
|
+
const head = path ? readHead(path) : null;
|
|
2602
|
+
if (!path || !head || !isHeifBytes(head)) return null;
|
|
2603
|
+
return { path, bytes: readFileSync3(path), label: `"${splitUrlSuffix(src).basePath}"` };
|
|
2604
|
+
}
|
|
2605
|
+
function isUsableFile2(path) {
|
|
2606
|
+
try {
|
|
2607
|
+
return statSync3(path).size > 0;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
|
|
2613
|
+
mkdirSync3(dirname4(cachePath), { recursive: true });
|
|
2614
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
2615
|
+
if (!isUsableFile2(cachePath)) {
|
|
2616
|
+
const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
2617
|
+
const inlinePath = `${tmpPath}.heic`;
|
|
2618
|
+
const inputPath = source.path ?? inlinePath;
|
|
2619
|
+
try {
|
|
2620
|
+
if (!source.path) writeFileSync2(inlinePath, source.bytes);
|
|
2621
|
+
await (options.transcode ?? convertHeifToJpeg)({
|
|
2622
|
+
inputPath,
|
|
2623
|
+
outputPath: tmpPath,
|
|
2624
|
+
timeoutMs: options.timeoutMs
|
|
2625
|
+
});
|
|
2626
|
+
if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
|
|
2627
|
+
renameWithRetry4(tmpPath, cachePath);
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2630
|
+
throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
|
|
2631
|
+
} finally {
|
|
2632
|
+
rmSync3(tmpPath, { force: true });
|
|
2633
|
+
rmSync3(inlinePath, { force: true });
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (resolve5(outputPath) !== resolve5(cachePath) && !isUsableFile2(outputPath)) {
|
|
2637
|
+
copyFileSync3(cachePath, outputPath);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
async function prepareHeifImageInputs(html, options) {
|
|
2641
|
+
const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_HEIF_SUBDIR);
|
|
2642
|
+
const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
|
|
2643
|
+
const cacheDir = options.cacheDir ?? outputDir;
|
|
2644
|
+
const preparedAssets = /* @__PURE__ */ new Map();
|
|
2645
|
+
const preparedImages = [];
|
|
2646
|
+
if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
|
|
2647
|
+
const { document: document2 } = parseHTML4(html);
|
|
2648
|
+
const found = [];
|
|
2649
|
+
for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
|
|
2650
|
+
const src = (img.getAttribute("src") || "").trim();
|
|
2651
|
+
const source = src ? resolveImageSource(src, options) : null;
|
|
2652
|
+
if (!source) continue;
|
|
2653
|
+
const hash = createHash3("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
|
|
2654
|
+
found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
|
|
2655
|
+
}
|
|
2656
|
+
if (found.length === 0) return { html, preparedAssets, preparedImages };
|
|
2657
|
+
const unique = new Map(found.map((item) => [item.filename, item.source]));
|
|
2658
|
+
const failed = /* @__PURE__ */ new Set();
|
|
2659
|
+
await Promise.all(
|
|
2660
|
+
[...unique].map(
|
|
2661
|
+
([filename, source]) => withMediaProbeSlot(
|
|
2662
|
+
() => ensurePreparedJpeg(source, join5(cacheDir, filename), join5(outputDir, filename), options)
|
|
2663
|
+
).catch((error) => {
|
|
2664
|
+
if (!options.onFailure) throw error;
|
|
2665
|
+
failed.add(filename);
|
|
2666
|
+
options.onFailure(error);
|
|
2667
|
+
})
|
|
2668
|
+
)
|
|
2669
|
+
);
|
|
2670
|
+
for (const { img, src, filename } of found) {
|
|
2671
|
+
if (failed.has(filename)) continue;
|
|
2672
|
+
const outputSrc = `${outputSrcPrefix}/${filename}`;
|
|
2673
|
+
img.setAttribute("src", outputSrc);
|
|
2674
|
+
preparedAssets.set(outputSrc, join5(outputDir, filename));
|
|
2675
|
+
preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join5(outputDir, filename) });
|
|
2676
|
+
}
|
|
2677
|
+
return { html: document2.toString(), preparedAssets, preparedImages };
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// src/services/htmlCompiler.ts
|
|
2681
|
+
import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
|
|
2682
|
+
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2683
|
+
|
|
2401
2684
|
// src/services/assetMediaType.ts
|
|
2685
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2686
|
+
import {
|
|
2687
|
+
fingerprintElementId,
|
|
2688
|
+
isNotMediaPayload,
|
|
2689
|
+
NotMediaPayloadError,
|
|
2690
|
+
probeMediaProfile,
|
|
2691
|
+
resolveProjectRelativeSrc as resolveProjectRelativeSrc2
|
|
2692
|
+
} from "@hyperframes/engine";
|
|
2402
2693
|
var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
|
|
2403
2694
|
var AssetMediaTypeMismatchError = class extends Error {
|
|
2404
2695
|
code = ASSET_MEDIA_TYPE_MISMATCH;
|
|
@@ -2508,7 +2799,7 @@ function logRemoteDownloadTelemetry(event) {
|
|
|
2508
2799
|
}
|
|
2509
2800
|
var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
|
|
2510
2801
|
function parseSubCompHtmlForValidity(html) {
|
|
2511
|
-
return
|
|
2802
|
+
return parseHTML5(html).document;
|
|
2512
2803
|
}
|
|
2513
2804
|
function endsWithSourceUrl(code) {
|
|
2514
2805
|
const trimmed = code.trimEnd();
|
|
@@ -2516,7 +2807,7 @@ function endsWithSourceUrl(code) {
|
|
|
2516
2807
|
return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
|
|
2517
2808
|
}
|
|
2518
2809
|
function prepareCompositionScripts(html) {
|
|
2519
|
-
const { document: document2 } =
|
|
2810
|
+
const { document: document2 } = parseHTML5(html);
|
|
2520
2811
|
for (const el of document2.querySelectorAll("script:not([src])")) {
|
|
2521
2812
|
const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
2522
2813
|
const runsAsScript = isModule || isJavaScriptType(el);
|
|
@@ -2553,14 +2844,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
2553
2844
|
}
|
|
2554
2845
|
};
|
|
2555
2846
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
2556
|
-
const { document: document2 } =
|
|
2847
|
+
const { document: document2 } = parseHTML5(html);
|
|
2557
2848
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
2558
2849
|
const problems = [];
|
|
2559
2850
|
for (const el of hosts) {
|
|
2560
2851
|
const srcPath = el.getAttribute("data-composition-src");
|
|
2561
2852
|
if (!srcPath) continue;
|
|
2562
2853
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
2563
|
-
const filePath =
|
|
2854
|
+
const filePath = resolve6(projectDir, srcPath);
|
|
2564
2855
|
if (visited.has(filePath)) continue;
|
|
2565
2856
|
const read = readProjectFile(filePath);
|
|
2566
2857
|
if (read.kind === "missing") {
|
|
@@ -2616,7 +2907,7 @@ function runtimeRoot(document2) {
|
|
|
2616
2907
|
}
|
|
2617
2908
|
function detectRenderModeHints(html, pageMotionBlur) {
|
|
2618
2909
|
const reasons = [];
|
|
2619
|
-
const { document: document2 } =
|
|
2910
|
+
const { document: document2 } = parseHTML5(html);
|
|
2620
2911
|
if (document2.querySelector("canvas[layoutsubtree]")) {
|
|
2621
2912
|
reasons.push({
|
|
2622
2913
|
code: "htmlInCanvas",
|
|
@@ -2658,7 +2949,7 @@ function detectThreeDTransformUsage(html) {
|
|
|
2658
2949
|
}
|
|
2659
2950
|
var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
|
|
2660
2951
|
function detectAncestorBackgroundImage(html) {
|
|
2661
|
-
const { document: document2 } =
|
|
2952
|
+
const { document: document2 } = parseHTML5(html);
|
|
2662
2953
|
const root = document2.querySelector("[data-composition-id]");
|
|
2663
2954
|
if (!root) return false;
|
|
2664
2955
|
const ancestors = [];
|
|
@@ -2705,7 +2996,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
2705
2996
|
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
2706
2997
|
let filePath = src;
|
|
2707
2998
|
if (isHttpUrl(src)) {
|
|
2708
|
-
if (!existsSync6(downloadDir))
|
|
2999
|
+
if (!existsSync6(downloadDir)) mkdirSync4(downloadDir, { recursive: true });
|
|
2709
3000
|
try {
|
|
2710
3001
|
filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
|
|
2711
3002
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -2715,7 +3006,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
2715
3006
|
}
|
|
2716
3007
|
} else {
|
|
2717
3008
|
filePath = decodedUrlPath(src);
|
|
2718
|
-
if (!filePath.startsWith("/")) filePath =
|
|
3009
|
+
if (!filePath.startsWith("/")) filePath = join6(baseDir, filePath);
|
|
2719
3010
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
2720
3011
|
}
|
|
2721
3012
|
if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
|
|
@@ -2772,7 +3063,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
2772
3063
|
function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
2773
3064
|
const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
|
|
2774
3065
|
if (unresolvedIds.size === 0) return html;
|
|
2775
|
-
const { document: document2 } =
|
|
3066
|
+
const { document: document2 } = parseHTML5(html);
|
|
2776
3067
|
let changed = false;
|
|
2777
3068
|
for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
|
|
2778
3069
|
if (!unresolvedIds.has(element.id)) continue;
|
|
@@ -2843,13 +3134,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
2843
3134
|
}
|
|
2844
3135
|
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
2845
3136
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
2846
|
-
const { document: document2 } =
|
|
3137
|
+
const { document: document2 } = parseHTML5(html);
|
|
2847
3138
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
2848
3139
|
const workItems = [];
|
|
2849
3140
|
for (const el of compEls) {
|
|
2850
3141
|
const srcPath = el.getAttribute("data-composition-src");
|
|
2851
3142
|
if (!srcPath) continue;
|
|
2852
|
-
const filePath =
|
|
3143
|
+
const filePath = resolve6(projectDir, srcPath);
|
|
2853
3144
|
if (visited.has(filePath)) {
|
|
2854
3145
|
continue;
|
|
2855
3146
|
}
|
|
@@ -2866,7 +3157,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
2866
3157
|
workItems.map(async (item) => {
|
|
2867
3158
|
const { html: compiledSub } = await compileHtmlFile(
|
|
2868
3159
|
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
2869
|
-
|
|
3160
|
+
dirname5(item.filePath),
|
|
2870
3161
|
downloadDir,
|
|
2871
3162
|
void 0,
|
|
2872
3163
|
urlByAbsentLocalPath
|
|
@@ -2894,7 +3185,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
2894
3185
|
return { subCompositions };
|
|
2895
3186
|
}
|
|
2896
3187
|
function promoteCssImportsToLinkTags(html) {
|
|
2897
|
-
const { document: document2 } =
|
|
3188
|
+
const { document: document2 } = parseHTML5(html);
|
|
2898
3189
|
const head = document2.querySelector("head");
|
|
2899
3190
|
if (!head) return html;
|
|
2900
3191
|
const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
|
|
@@ -2963,7 +3254,7 @@ var ProducerHostIdentityMap = class extends Map {
|
|
|
2963
3254
|
}
|
|
2964
3255
|
};
|
|
2965
3256
|
function coalesceHeadStylesAndBodyScripts(html) {
|
|
2966
|
-
const { document: document2 } =
|
|
3257
|
+
const { document: document2 } = parseHTML5(html);
|
|
2967
3258
|
const head = document2.querySelector("head");
|
|
2968
3259
|
const body = document2.querySelector("body");
|
|
2969
3260
|
if (!head && !body) return html;
|
|
@@ -3014,7 +3305,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
|
|
|
3014
3305
|
return document2.toString();
|
|
3015
3306
|
}
|
|
3016
3307
|
function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
|
|
3017
|
-
const { document: document2 } =
|
|
3308
|
+
const { document: document2 } = parseHTML5(html);
|
|
3018
3309
|
const head = document2.querySelector("head");
|
|
3019
3310
|
const body = document2.querySelector("body");
|
|
3020
3311
|
const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
|
|
@@ -3043,17 +3334,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3043
3334
|
resolveHtml: (srcPath) => {
|
|
3044
3335
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
3045
3336
|
if (!compHtml) {
|
|
3046
|
-
const read = readProjectFile(
|
|
3337
|
+
const read = readProjectFile(resolve6(projectDir, srcPath));
|
|
3047
3338
|
if (read.kind === "file") {
|
|
3048
3339
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
3049
3340
|
}
|
|
3050
3341
|
}
|
|
3051
3342
|
return compHtml;
|
|
3052
3343
|
},
|
|
3053
|
-
parseHtml: (htmlStr) =>
|
|
3344
|
+
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
3054
3345
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
3055
3346
|
// against its own directory, project-root refs stay as authored.
|
|
3056
|
-
assetExists: (path) => existsSync6(
|
|
3347
|
+
assetExists: (path) => existsSync6(resolve6(projectDir, path)),
|
|
3057
3348
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
3058
3349
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
3059
3350
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -3154,7 +3445,7 @@ ${html}
|
|
|
3154
3445
|
</html>`;
|
|
3155
3446
|
}
|
|
3156
3447
|
function injectTextRenderingRule(html) {
|
|
3157
|
-
const { document: document2 } =
|
|
3448
|
+
const { document: document2 } = parseHTML5(html);
|
|
3158
3449
|
const head = document2.querySelector("head");
|
|
3159
3450
|
if (!head) return html;
|
|
3160
3451
|
if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
|
|
@@ -3178,7 +3469,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
3178
3469
|
(alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
|
|
3179
3470
|
);
|
|
3180
3471
|
if (!strongest) return true;
|
|
3181
|
-
const actual =
|
|
3472
|
+
const actual = createHash4(strongest).update(bytes).digest();
|
|
3182
3473
|
return hashes.some(
|
|
3183
3474
|
(hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
|
|
3184
3475
|
);
|
|
@@ -3186,7 +3477,7 @@ function matchesScriptIntegrity(bytes, metadata) {
|
|
|
3186
3477
|
async function inlineExternalScripts(html) {
|
|
3187
3478
|
const fullHtml = ensureFullDocument(html);
|
|
3188
3479
|
const wrappedFragment = fullHtml !== html;
|
|
3189
|
-
const { document: document2 } =
|
|
3480
|
+
const { document: document2 } = parseHTML5(fullHtml);
|
|
3190
3481
|
const scripts = document2.querySelectorAll("script[src]");
|
|
3191
3482
|
const externalScripts = [];
|
|
3192
3483
|
for (const el of scripts) {
|
|
@@ -3235,12 +3526,12 @@ ${safeText}
|
|
|
3235
3526
|
return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
|
|
3236
3527
|
}
|
|
3237
3528
|
function collectExternalAssets(html, projectDir) {
|
|
3238
|
-
const absProjectDir =
|
|
3529
|
+
const absProjectDir = resolve6(projectDir);
|
|
3239
3530
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
3240
3531
|
function processPath(rawPath) {
|
|
3241
3532
|
const trimmed = rawPath.trim();
|
|
3242
3533
|
if (isNonRelativeUrl(trimmed)) return null;
|
|
3243
|
-
const absPath =
|
|
3534
|
+
const absPath = resolve6(absProjectDir, trimmed);
|
|
3244
3535
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
3245
3536
|
return null;
|
|
3246
3537
|
}
|
|
@@ -3249,7 +3540,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
3249
3540
|
externalAssets.set(safeKey, absPath);
|
|
3250
3541
|
return safeKey;
|
|
3251
3542
|
}
|
|
3252
|
-
const { document: document2 } =
|
|
3543
|
+
const { document: document2 } = parseHTML5(html);
|
|
3253
3544
|
for (const el of document2.querySelectorAll("[src], [href]")) {
|
|
3254
3545
|
for (const attr of ["src", "href"]) {
|
|
3255
3546
|
const val = (el.getAttribute(attr) || "").trim();
|
|
@@ -3294,7 +3585,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
3294
3585
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
3295
3586
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
3296
3587
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
3297
|
-
if (!existsSync6(remoteDir))
|
|
3588
|
+
if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3298
3589
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
3299
3590
|
await Promise.all(
|
|
3300
3591
|
[...urlSet].map(async (url) => {
|
|
@@ -3341,7 +3632,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
|
|
|
3341
3632
|
return downloadAndRewriteUrls(
|
|
3342
3633
|
urlSet,
|
|
3343
3634
|
html,
|
|
3344
|
-
|
|
3635
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3345
3636
|
"Remote media download failed",
|
|
3346
3637
|
"Localized remote media source(s)"
|
|
3347
3638
|
);
|
|
@@ -3356,7 +3647,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
|
|
|
3356
3647
|
return downloadAndRewriteUrls(
|
|
3357
3648
|
urlSet,
|
|
3358
3649
|
html,
|
|
3359
|
-
|
|
3650
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3360
3651
|
"Remote image download failed",
|
|
3361
3652
|
"Localized remote image source(s)"
|
|
3362
3653
|
);
|
|
@@ -3372,7 +3663,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
|
|
|
3372
3663
|
return downloadAndRewriteUrls(
|
|
3373
3664
|
urlSet,
|
|
3374
3665
|
html,
|
|
3375
|
-
|
|
3666
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3376
3667
|
"Remote background-image download failed",
|
|
3377
3668
|
"Localized remote background-image(s)",
|
|
3378
3669
|
// Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
|
|
@@ -3507,7 +3798,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
|
|
|
3507
3798
|
return downloadAndRewriteUrls(
|
|
3508
3799
|
urlSet,
|
|
3509
3800
|
processed,
|
|
3510
|
-
|
|
3801
|
+
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3511
3802
|
"Remote font download failed",
|
|
3512
3803
|
"Localized remote font face(s)",
|
|
3513
3804
|
(h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
|
|
@@ -3552,7 +3843,7 @@ async function embedLocalFontFaces(html, projectDir) {
|
|
|
3552
3843
|
while ((urlMatch = urlRe.exec(block)) !== null) {
|
|
3553
3844
|
const localPath = urlMatch[1];
|
|
3554
3845
|
if (!localPath || embeddedPaths.has(localPath)) continue;
|
|
3555
|
-
const absPath = localPath.startsWith("/") ? localPath :
|
|
3846
|
+
const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
|
|
3556
3847
|
if (!isPathInside2(absPath, projectDir)) continue;
|
|
3557
3848
|
const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
|
|
3558
3849
|
try {
|
|
@@ -3601,7 +3892,7 @@ function renderGsapDist() {
|
|
|
3601
3892
|
}
|
|
3602
3893
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
3603
3894
|
let gsapDist;
|
|
3604
|
-
const exists = (path) => existsSync6(
|
|
3895
|
+
const exists = (path) => existsSync6(resolve6(projectDir, path));
|
|
3605
3896
|
return html.replace(
|
|
3606
3897
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
3607
3898
|
(full, prefix, src, file, suffix) => {
|
|
@@ -3617,8 +3908,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
3617
3908
|
if (!isPathInside2(htmlPath, projectDir)) return html;
|
|
3618
3909
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
3619
3910
|
if (!entryPath.includes("/")) return html;
|
|
3620
|
-
const { document: document2 } =
|
|
3621
|
-
const assetExists = (path) => existsSync6(
|
|
3911
|
+
const { document: document2 } = parseHTML5(html);
|
|
3912
|
+
const assetExists = (path) => existsSync6(resolve6(projectDir, path));
|
|
3622
3913
|
rewriteAssetPaths(
|
|
3623
3914
|
document2.querySelectorAll("[src], [href]"),
|
|
3624
3915
|
entryPath,
|
|
@@ -3640,7 +3931,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
3640
3931
|
}
|
|
3641
3932
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
3642
3933
|
const entryHtml = rebaseDirectEntryAssetPaths(
|
|
3643
|
-
|
|
3934
|
+
readFileSync4(htmlPath, "utf-8"),
|
|
3644
3935
|
projectDir,
|
|
3645
3936
|
htmlPath
|
|
3646
3937
|
);
|
|
@@ -3710,12 +4001,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3710
4001
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
3711
4002
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
3712
4003
|
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
|
|
4004
|
+
const {
|
|
4005
|
+
html: htmlWithJpegs,
|
|
4006
|
+
preparedAssets: preparedHeifAssets,
|
|
4007
|
+
preparedImages: preparedHeifImages
|
|
4008
|
+
} = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
|
|
4009
|
+
projectDir,
|
|
4010
|
+
downloadDir,
|
|
4011
|
+
cacheDir: options.animatedGifCacheDir,
|
|
4012
|
+
sourceAssets: remoteImageAssets,
|
|
4013
|
+
timeoutMs: options.ffmpegProcessTimeout
|
|
4014
|
+
});
|
|
4015
|
+
if (preparedHeifImages.length > 0) {
|
|
4016
|
+
defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
|
|
4017
|
+
}
|
|
3713
4018
|
const gifSourceAssets = new Map(remoteImageAssets);
|
|
3714
4019
|
const {
|
|
3715
4020
|
html: htmlWithPreparedGifs,
|
|
3716
4021
|
preparedAssets: preparedGifAssets,
|
|
3717
4022
|
preparedGifs
|
|
3718
|
-
} = await prepareAnimatedGifInputs(
|
|
4023
|
+
} = await prepareAnimatedGifInputs(htmlWithJpegs, {
|
|
3719
4024
|
projectDir,
|
|
3720
4025
|
downloadDir,
|
|
3721
4026
|
cacheDir: options.animatedGifCacheDir,
|
|
@@ -3742,11 +4047,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3742
4047
|
for (const [relPath, absPath] of preparedGifAssets) {
|
|
3743
4048
|
externalAssets.set(relPath, absPath);
|
|
3744
4049
|
}
|
|
4050
|
+
for (const [relPath, absPath] of preparedHeifAssets) {
|
|
4051
|
+
externalAssets.set(relPath, absPath);
|
|
4052
|
+
}
|
|
3745
4053
|
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
3746
4054
|
const masterFxChain = parseMasterFxChain(html);
|
|
3747
4055
|
for (const video of videos) {
|
|
3748
4056
|
if (isHttpUrl(video.src)) continue;
|
|
3749
|
-
const videoPath =
|
|
4057
|
+
const videoPath = resolve6(projectDir, video.src);
|
|
3750
4058
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
3751
4059
|
(async () => {
|
|
3752
4060
|
const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
|
|
@@ -3766,7 +4074,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
3766
4074
|
})().catch(() => {
|
|
3767
4075
|
});
|
|
3768
4076
|
}
|
|
3769
|
-
const { document: document2 } =
|
|
4077
|
+
const { document: document2 } = parseHTML5(html);
|
|
3770
4078
|
const rootEl = document2.querySelector("[data-composition-id]");
|
|
3771
4079
|
const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
|
|
3772
4080
|
const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
|
|
@@ -4173,7 +4481,7 @@ async function runCompileStage(input) {
|
|
|
4173
4481
|
abortSignal
|
|
4174
4482
|
} = input;
|
|
4175
4483
|
const compileStart = Date.now();
|
|
4176
|
-
const compiled = await compileForRender(projectDir, htmlPath,
|
|
4484
|
+
const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
|
|
4177
4485
|
log,
|
|
4178
4486
|
failClosedFontFetch: failClosedFontFetch === true,
|
|
4179
4487
|
allowSystemFontCapture,
|
|
@@ -4181,7 +4489,7 @@ async function runCompileStage(input) {
|
|
|
4181
4489
|
variables: input.variables,
|
|
4182
4490
|
urlByAbsentLocalPath: input.urlByAbsentLocalPath,
|
|
4183
4491
|
pageMotionBlur: input.pageMotionBlur,
|
|
4184
|
-
animatedGifCacheDir: cfg.extractCacheDir ?
|
|
4492
|
+
animatedGifCacheDir: cfg.extractCacheDir ? join7(cfg.extractCacheDir, "animated-gif") : void 0,
|
|
4185
4493
|
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
|
|
4186
4494
|
});
|
|
4187
4495
|
assertNotAborted();
|
|
@@ -4237,7 +4545,7 @@ async function runCompileStage(input) {
|
|
|
4237
4545
|
};
|
|
4238
4546
|
await preflightCompositionAssetMediaTypes({
|
|
4239
4547
|
projectDir,
|
|
4240
|
-
compiledDir:
|
|
4548
|
+
compiledDir: join7(workDir, "compiled"),
|
|
4241
4549
|
composition,
|
|
4242
4550
|
signal: abortSignal
|
|
4243
4551
|
});
|
|
@@ -4282,8 +4590,8 @@ async function runCompileStage(input) {
|
|
|
4282
4590
|
}
|
|
4283
4591
|
|
|
4284
4592
|
// src/services/render/stages/probeStage.ts
|
|
4285
|
-
import { join as
|
|
4286
|
-
import { parseHTML as
|
|
4593
|
+
import { join as join8 } from "node:path";
|
|
4594
|
+
import { parseHTML as parseHTML6 } from "linkedom";
|
|
4287
4595
|
import {
|
|
4288
4596
|
closeCaptureSession as closeCaptureSession4,
|
|
4289
4597
|
compositionRequiresWebGpu,
|
|
@@ -4330,7 +4638,7 @@ function durationToFrameCount(duration, fps) {
|
|
|
4330
4638
|
}
|
|
4331
4639
|
function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
4332
4640
|
if (audioCount <= 0) return false;
|
|
4333
|
-
const { document: document2 } =
|
|
4641
|
+
const { document: document2 } = parseHTML6(html);
|
|
4334
4642
|
const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
|
|
4335
4643
|
if (!scriptBodies) return false;
|
|
4336
4644
|
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(
|
|
@@ -4338,12 +4646,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
|
|
|
4338
4646
|
);
|
|
4339
4647
|
}
|
|
4340
4648
|
function hasAutoStartVideos(html) {
|
|
4341
|
-
const { document: document2 } =
|
|
4649
|
+
const { document: document2 } = parseHTML6(html);
|
|
4342
4650
|
return document2.querySelector("video[data-hf-auto-start]") !== null;
|
|
4343
4651
|
}
|
|
4344
4652
|
function hasVariableBoundMedia(html, variables) {
|
|
4345
4653
|
if (!variables || Object.keys(variables).length === 0) return false;
|
|
4346
|
-
const { document: document2 } =
|
|
4654
|
+
const { document: document2 } = parseHTML6(html);
|
|
4347
4655
|
return Array.from(
|
|
4348
4656
|
document2.querySelectorAll(
|
|
4349
4657
|
"img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
|
|
@@ -4354,7 +4662,7 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
4354
4662
|
});
|
|
4355
4663
|
}
|
|
4356
4664
|
function authoredMediaStarts(html) {
|
|
4357
|
-
const { document: document2 } =
|
|
4665
|
+
const { document: document2 } = parseHTML6(html);
|
|
4358
4666
|
const starts = /* @__PURE__ */ new Map();
|
|
4359
4667
|
for (const media of document2.querySelectorAll("video, audio")) {
|
|
4360
4668
|
const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
|
|
@@ -4391,7 +4699,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
|
|
|
4391
4699
|
return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
|
|
4392
4700
|
}
|
|
4393
4701
|
function hasRuntimeMediaChanges(html) {
|
|
4394
|
-
const { document: document2 } =
|
|
4702
|
+
const { document: document2 } = parseHTML6(html);
|
|
4395
4703
|
const scriptBodies = [...document2.querySelectorAll("script")].map(
|
|
4396
4704
|
(script) => script.textContent ?? ""
|
|
4397
4705
|
);
|
|
@@ -4474,7 +4782,7 @@ async function runProbeStage(input) {
|
|
|
4474
4782
|
});
|
|
4475
4783
|
fileServer = await createFileServer({
|
|
4476
4784
|
projectDir,
|
|
4477
|
-
compiledDir:
|
|
4785
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4478
4786
|
port: 0,
|
|
4479
4787
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
4480
4788
|
fps: job.config.fps
|
|
@@ -4503,7 +4811,7 @@ async function runProbeStage(input) {
|
|
|
4503
4811
|
log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
|
|
4504
4812
|
probeSession = await createCaptureSession4(
|
|
4505
4813
|
fileServer.url,
|
|
4506
|
-
|
|
4814
|
+
join8(workDir, "probe"),
|
|
4507
4815
|
captureOpts,
|
|
4508
4816
|
null,
|
|
4509
4817
|
probeCfg
|
|
@@ -4588,7 +4896,7 @@ async function runProbeStage(input) {
|
|
|
4588
4896
|
});
|
|
4589
4897
|
probeSession = await createCaptureSession4(
|
|
4590
4898
|
fileServer.url,
|
|
4591
|
-
|
|
4899
|
+
join8(workDir, "probe-screenshot"),
|
|
4592
4900
|
captureOpts,
|
|
4593
4901
|
null,
|
|
4594
4902
|
{ ...probeCfg, forceScreenshot: true }
|
|
@@ -4624,7 +4932,7 @@ async function runProbeStage(input) {
|
|
|
4624
4932
|
compiled,
|
|
4625
4933
|
resolutions,
|
|
4626
4934
|
projectDir,
|
|
4627
|
-
|
|
4935
|
+
join8(workDir, "downloads")
|
|
4628
4936
|
);
|
|
4629
4937
|
assertNotAborted();
|
|
4630
4938
|
composition.videos = compiled.videos;
|
|
@@ -4815,7 +5123,7 @@ async function runProbeStage(input) {
|
|
|
4815
5123
|
}
|
|
4816
5124
|
await preflightCompositionAssetMediaTypes({
|
|
4817
5125
|
projectDir,
|
|
4818
|
-
compiledDir:
|
|
5126
|
+
compiledDir: join8(workDir, "compiled"),
|
|
4819
5127
|
composition,
|
|
4820
5128
|
signal: abortSignal
|
|
4821
5129
|
});
|
|
@@ -4980,7 +5288,7 @@ function validateDistributedDuration(input) {
|
|
|
4980
5288
|
|
|
4981
5289
|
// src/services/render/stages/extractVideosStage.ts
|
|
4982
5290
|
import { existsSync as existsSync7 } from "node:fs";
|
|
4983
|
-
import { join as
|
|
5291
|
+
import { join as join9 } from "node:path";
|
|
4984
5292
|
import {
|
|
4985
5293
|
classifyVideoExtractionError,
|
|
4986
5294
|
createFrameLookupTable,
|
|
@@ -5300,14 +5608,14 @@ async function runExtractVideosStage(input) {
|
|
|
5300
5608
|
// because short boundary counts can differ by one frame.
|
|
5301
5609
|
{
|
|
5302
5610
|
fps: job.config.fps,
|
|
5303
|
-
outputDir:
|
|
5611
|
+
outputDir: join9(compiledDir, "__hyperframes_video_frames"),
|
|
5304
5612
|
format: job.config.videoFrameFormat ?? "auto",
|
|
5305
5613
|
toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
|
|
5306
5614
|
timelineEnd: composition.duration,
|
|
5307
5615
|
maxTransientRetries: extractionPolicy.maxTransientRetries,
|
|
5308
5616
|
collectProbeFailures: extractionPolicy.failureMode === "enforce",
|
|
5309
5617
|
deferRangeExtraction,
|
|
5310
|
-
contentKeyedDirs: [
|
|
5618
|
+
contentKeyedDirs: [join9(compiledDir, REMOTE_MEDIA_SUBDIR)],
|
|
5311
5619
|
urlByAbsentLocalPath
|
|
5312
5620
|
},
|
|
5313
5621
|
abortSignal,
|
|
@@ -5401,8 +5709,8 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
5401
5709
|
}
|
|
5402
5710
|
|
|
5403
5711
|
// src/services/render/stages/audioStage.ts
|
|
5404
|
-
import { join as
|
|
5405
|
-
import { rmSync as
|
|
5712
|
+
import { join as join11 } from "node:path";
|
|
5713
|
+
import { rmSync as rmSync5 } from "node:fs";
|
|
5406
5714
|
import {
|
|
5407
5715
|
MIXED_AUDIO_FILENAME,
|
|
5408
5716
|
audioFailureDiagnostics,
|
|
@@ -5411,8 +5719,8 @@ import {
|
|
|
5411
5719
|
|
|
5412
5720
|
// src/services/render/audioPadTrim.ts
|
|
5413
5721
|
import { spawn } from "node:child_process";
|
|
5414
|
-
import { mkdtempSync, rmSync as
|
|
5415
|
-
import { dirname as
|
|
5722
|
+
import { mkdtempSync, rmSync as rmSync4 } from "node:fs";
|
|
5723
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
5416
5724
|
import {
|
|
5417
5725
|
DEFAULT_CONFIG,
|
|
5418
5726
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
@@ -5420,8 +5728,8 @@ import {
|
|
|
5420
5728
|
extractAudioMetadata as extractAudioMetadata3,
|
|
5421
5729
|
getFfprobeBinary,
|
|
5422
5730
|
ManagedChildProcess,
|
|
5423
|
-
renameWithRetry as
|
|
5424
|
-
runFfmpeg as
|
|
5731
|
+
renameWithRetry as renameWithRetry5,
|
|
5732
|
+
runFfmpeg as runFfmpeg5,
|
|
5425
5733
|
trackChildProcess
|
|
5426
5734
|
} from "@hyperframes/engine";
|
|
5427
5735
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
@@ -5431,7 +5739,7 @@ import {
|
|
|
5431
5739
|
aacSegmentCount,
|
|
5432
5740
|
describeFfmpegFailure,
|
|
5433
5741
|
extractAudioMetadata as extractAudioMetadata2,
|
|
5434
|
-
runFfmpeg as
|
|
5742
|
+
runFfmpeg as runFfmpeg4
|
|
5435
5743
|
} from "@hyperframes/engine";
|
|
5436
5744
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
5437
5745
|
function checkedDbfs(value) {
|
|
@@ -5455,7 +5763,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
5455
5763
|
else laterBlocks = Math.max(laterBlocks, block);
|
|
5456
5764
|
}
|
|
5457
5765
|
};
|
|
5458
|
-
const result = await
|
|
5766
|
+
const result = await runFfmpeg4(
|
|
5459
5767
|
["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
|
|
5460
5768
|
{ signal, timeout: timeoutMs, onStderr: readLines }
|
|
5461
5769
|
);
|
|
@@ -5615,7 +5923,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
5615
5923
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
5616
5924
|
};
|
|
5617
5925
|
} finally {
|
|
5618
|
-
for (const path of plan2.cleanupPaths)
|
|
5926
|
+
for (const path of plan2.cleanupPaths) rmSync4(path, { force: true });
|
|
5619
5927
|
}
|
|
5620
5928
|
let loweredDb = 0;
|
|
5621
5929
|
if (probeTruePeak) {
|
|
@@ -5662,8 +5970,8 @@ async function constrainAacTruePeak(input) {
|
|
|
5662
5970
|
async function enforceAacTruePeak(input) {
|
|
5663
5971
|
let scratchDir;
|
|
5664
5972
|
try {
|
|
5665
|
-
scratchDir = mkdtempSync(
|
|
5666
|
-
const correctedPath =
|
|
5973
|
+
scratchDir = mkdtempSync(join10(dirname6(input.audioPath), ".true-peak-"));
|
|
5974
|
+
const correctedPath = join10(scratchDir, "audio.m4a");
|
|
5667
5975
|
const measurements = [];
|
|
5668
5976
|
let attenuationDb = 0;
|
|
5669
5977
|
let sourcePeakDbfs;
|
|
@@ -5678,7 +5986,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5678
5986
|
}
|
|
5679
5987
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
5680
5988
|
if (measuredPath !== correctedPath) return { success: true };
|
|
5681
|
-
|
|
5989
|
+
renameWithRetry5(correctedPath, input.audioPath);
|
|
5682
5990
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
5683
5991
|
}
|
|
5684
5992
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -5719,7 +6027,7 @@ async function enforceAacTruePeak(input) {
|
|
|
5719
6027
|
)}`
|
|
5720
6028
|
};
|
|
5721
6029
|
} finally {
|
|
5722
|
-
if (scratchDir)
|
|
6030
|
+
if (scratchDir) rmSync4(scratchDir, { recursive: true, force: true });
|
|
5723
6031
|
}
|
|
5724
6032
|
}
|
|
5725
6033
|
function failResult(outputPath, target, source, error) {
|
|
@@ -5796,7 +6104,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
5796
6104
|
};
|
|
5797
6105
|
}
|
|
5798
6106
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
5799
|
-
return toStepResult(await
|
|
6107
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
5800
6108
|
}
|
|
5801
6109
|
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
5802
6110
|
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
@@ -5866,7 +6174,7 @@ function assertAudioNotInterrupted(failures) {
|
|
|
5866
6174
|
}
|
|
5867
6175
|
async function runAudioStage(input) {
|
|
5868
6176
|
const stageStart = Date.now();
|
|
5869
|
-
const pcmSourcePath =
|
|
6177
|
+
const pcmSourcePath = join11(input.workDir, "audio-source.wav");
|
|
5870
6178
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
5871
6179
|
try {
|
|
5872
6180
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
@@ -5886,7 +6194,7 @@ async function runAudioStage(input) {
|
|
|
5886
6194
|
}
|
|
5887
6195
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
5888
6196
|
} finally {
|
|
5889
|
-
|
|
6197
|
+
rmSync5(pcmSourcePath, { force: true });
|
|
5890
6198
|
}
|
|
5891
6199
|
}
|
|
5892
6200
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -5904,7 +6212,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5904
6212
|
assertNotAborted,
|
|
5905
6213
|
log
|
|
5906
6214
|
} = input;
|
|
5907
|
-
const audioOutputPath =
|
|
6215
|
+
const audioOutputPath = join11(workDir, MIXED_AUDIO_FILENAME);
|
|
5908
6216
|
let hasAudio = false;
|
|
5909
6217
|
let audioError;
|
|
5910
6218
|
let audioFailures;
|
|
@@ -5915,7 +6223,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5915
6223
|
audioResult = await processCompositionAudio(
|
|
5916
6224
|
audios,
|
|
5917
6225
|
projectDir,
|
|
5918
|
-
|
|
6226
|
+
join11(workDir, "audio-work"),
|
|
5919
6227
|
audioOutputPath,
|
|
5920
6228
|
duration,
|
|
5921
6229
|
abortSignal,
|
|
@@ -5984,7 +6292,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
5984
6292
|
|
|
5985
6293
|
// src/services/render/stages/captureStage.ts
|
|
5986
6294
|
import { existsSync as existsSync8, readdirSync, statfsSync } from "node:fs";
|
|
5987
|
-
import { join as
|
|
6295
|
+
import { join as join12 } from "node:path";
|
|
5988
6296
|
import {
|
|
5989
6297
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
5990
6298
|
frameFileExtension,
|
|
@@ -6038,13 +6346,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
6038
6346
|
if (!existsSync8(workDir)) return total;
|
|
6039
6347
|
for (const attempt of readdirSync(workDir)) {
|
|
6040
6348
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6041
|
-
total += sampleDirectoryBytes(
|
|
6349
|
+
total += sampleDirectoryBytes(join12(workDir, attempt));
|
|
6042
6350
|
continue;
|
|
6043
6351
|
}
|
|
6044
6352
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
6045
|
-
const attemptDir =
|
|
6353
|
+
const attemptDir = join12(workDir, attempt);
|
|
6046
6354
|
for (const name of readdirSync(attemptDir)) {
|
|
6047
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
6355
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join12(attemptDir, name));
|
|
6048
6356
|
}
|
|
6049
6357
|
}
|
|
6050
6358
|
return total;
|
|
@@ -6370,24 +6678,24 @@ import {
|
|
|
6370
6678
|
|
|
6371
6679
|
// src/services/render/stages/encodeStage.ts
|
|
6372
6680
|
import {
|
|
6373
|
-
copyFileSync as
|
|
6681
|
+
copyFileSync as copyFileSync4,
|
|
6374
6682
|
existsSync as existsSync9,
|
|
6375
|
-
mkdirSync as
|
|
6683
|
+
mkdirSync as mkdirSync5,
|
|
6376
6684
|
readdirSync as readdirSync2,
|
|
6377
|
-
readFileSync as
|
|
6378
|
-
rmSync as
|
|
6379
|
-
statSync as
|
|
6380
|
-
writeFileSync as
|
|
6685
|
+
readFileSync as readFileSync5,
|
|
6686
|
+
rmSync as rmSync6,
|
|
6687
|
+
statSync as statSync4,
|
|
6688
|
+
writeFileSync as writeFileSync3
|
|
6381
6689
|
} from "node:fs";
|
|
6382
6690
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6383
|
-
import { dirname as
|
|
6691
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
6384
6692
|
import {
|
|
6385
6693
|
encodeFramesChunkedConcat,
|
|
6386
6694
|
encodeFramesFromDir,
|
|
6387
6695
|
formatFfmpegError,
|
|
6388
6696
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
6389
6697
|
resolveConfig,
|
|
6390
|
-
runFfmpeg as
|
|
6698
|
+
runFfmpeg as runFfmpeg6,
|
|
6391
6699
|
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
6392
6700
|
} from "@hyperframes/engine";
|
|
6393
6701
|
import {
|
|
@@ -6463,17 +6771,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
|
|
|
6463
6771
|
}
|
|
6464
6772
|
async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
6465
6773
|
const { outputPath } = argsInput;
|
|
6466
|
-
const result = await
|
|
6774
|
+
const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
|
|
6467
6775
|
if (!result.success) {
|
|
6468
6776
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
6469
6777
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
6470
6778
|
}
|
|
6471
|
-
const gif =
|
|
6779
|
+
const gif = readFileSync5(outputPath);
|
|
6472
6780
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
6473
6781
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
6474
6782
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
6475
6783
|
}
|
|
6476
|
-
|
|
6784
|
+
writeFileSync3(outputPath, gif);
|
|
6477
6785
|
return null;
|
|
6478
6786
|
}
|
|
6479
6787
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -6495,17 +6803,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
6495
6803
|
try {
|
|
6496
6804
|
const run = { signal: input.signal, timeout: input.timeout };
|
|
6497
6805
|
for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
|
|
6498
|
-
const result = await
|
|
6806
|
+
const result = await runFfmpeg6(args, run);
|
|
6499
6807
|
if (!result.success) {
|
|
6500
6808
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
6501
6809
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
6502
6810
|
}
|
|
6503
6811
|
}
|
|
6504
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
6812
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
|
|
6505
6813
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
6506
6814
|
if (failure) return failure;
|
|
6507
6815
|
}
|
|
6508
|
-
const fileSize = existsSync9(outputPath) ?
|
|
6816
|
+
const fileSize = existsSync9(outputPath) ? statSync4(outputPath).size : 0;
|
|
6509
6817
|
return {
|
|
6510
6818
|
success: true,
|
|
6511
6819
|
outputPath,
|
|
@@ -6514,7 +6822,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
6514
6822
|
fileSize
|
|
6515
6823
|
};
|
|
6516
6824
|
} finally {
|
|
6517
|
-
|
|
6825
|
+
rmSync6(input.palettePath, { force: true });
|
|
6518
6826
|
}
|
|
6519
6827
|
}
|
|
6520
6828
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -6586,10 +6894,10 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
6586
6894
|
const stream = await encoder;
|
|
6587
6895
|
for (let next = 0; next < totalFrames; next++) {
|
|
6588
6896
|
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
6589
|
-
await new Promise((
|
|
6897
|
+
await new Promise((resolve10) => wake = resolve10);
|
|
6590
6898
|
}
|
|
6591
6899
|
if (signal.aborted) return;
|
|
6592
|
-
const mergedPath =
|
|
6900
|
+
const mergedPath = join13(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
6593
6901
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
6594
6902
|
landed.delete(next);
|
|
6595
6903
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -6664,7 +6972,7 @@ async function runEncodeStage(input) {
|
|
|
6664
6972
|
const stage5Start = Date.now();
|
|
6665
6973
|
if (isPngSequence) {
|
|
6666
6974
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
6667
|
-
if (!existsSync9(outputPath))
|
|
6975
|
+
if (!existsSync9(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
6668
6976
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
6669
6977
|
if (captured.length === 0) {
|
|
6670
6978
|
throw new Error(
|
|
@@ -6672,11 +6980,11 @@ async function runEncodeStage(input) {
|
|
|
6672
6980
|
);
|
|
6673
6981
|
}
|
|
6674
6982
|
captured.forEach((name, i) => {
|
|
6675
|
-
const dst =
|
|
6676
|
-
|
|
6983
|
+
const dst = join13(outputPath, formatExportFrameName(i, "png"));
|
|
6984
|
+
copyFileSync4(join13(framesDir, name), dst);
|
|
6677
6985
|
});
|
|
6678
6986
|
if (hasAudio && audioOutputPath && existsSync9(audioOutputPath)) {
|
|
6679
|
-
|
|
6987
|
+
copyFileSync4(audioOutputPath, join13(outputPath, MIXED_AUDIO_FILENAME2));
|
|
6680
6988
|
log.info(
|
|
6681
6989
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
6682
6990
|
);
|
|
@@ -6696,7 +7004,7 @@ async function runEncodeStage(input) {
|
|
|
6696
7004
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
6697
7005
|
fps: job.config.fps,
|
|
6698
7006
|
loop,
|
|
6699
|
-
palettePath:
|
|
7007
|
+
palettePath: join13(dirname7(videoOnlyPath), "gif-palette.png"),
|
|
6700
7008
|
preserveAlpha: needsAlpha,
|
|
6701
7009
|
signal: abortSignal,
|
|
6702
7010
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -6755,9 +7063,9 @@ function sampleDirectoryBytes(dir) {
|
|
|
6755
7063
|
continue;
|
|
6756
7064
|
}
|
|
6757
7065
|
for (const name of entries) {
|
|
6758
|
-
const full =
|
|
7066
|
+
const full = join14(current, name);
|
|
6759
7067
|
try {
|
|
6760
|
-
const st =
|
|
7068
|
+
const st = statSync5(full);
|
|
6761
7069
|
if (st.isDirectory()) {
|
|
6762
7070
|
stack.push(full);
|
|
6763
7071
|
} else if (st.isFile()) {
|
|
@@ -6841,8 +7149,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
6841
7149
|
const ranges = [];
|
|
6842
7150
|
let rangeStart = null;
|
|
6843
7151
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
6844
|
-
const framePath =
|
|
6845
|
-
const missing = !existsSync10(framePath) ||
|
|
7152
|
+
const framePath = join14(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7153
|
+
const missing = !existsSync10(framePath) || statSync5(framePath).size <= 8;
|
|
6846
7154
|
if (missing && rangeStart === null) {
|
|
6847
7155
|
rangeStart = frameIndex;
|
|
6848
7156
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -6858,7 +7166,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
6858
7166
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
6859
7167
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
6860
7168
|
const batches = [];
|
|
6861
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
7169
|
+
const outputDirFor = (batchIndex, workerId) => join14(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
6862
7170
|
if (stride > 1) {
|
|
6863
7171
|
const spans = /* @__PURE__ */ new Map();
|
|
6864
7172
|
for (const range of ranges) {
|
|
@@ -6926,7 +7234,7 @@ function isRecoverableParallelCaptureError(error) {
|
|
|
6926
7234
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
6927
7235
|
let captured = 0;
|
|
6928
7236
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
6929
|
-
const framePath =
|
|
7237
|
+
const framePath = join14(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
6930
7238
|
if (existsSync10(framePath)) captured++;
|
|
6931
7239
|
}
|
|
6932
7240
|
return captured;
|
|
@@ -6952,7 +7260,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
6952
7260
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
6953
7261
|
});
|
|
6954
7262
|
pendingTransientRetry = false;
|
|
6955
|
-
const attemptWorkDir =
|
|
7263
|
+
const attemptWorkDir = join14(options.workDir, `capture-attempt-${attempt}`);
|
|
6956
7264
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
6957
7265
|
missingRanges,
|
|
6958
7266
|
currentWorkers,
|
|
@@ -7143,7 +7451,7 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
|
|
|
7143
7451
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
7144
7452
|
|
|
7145
7453
|
// src/services/distributed/planFrameCost.ts
|
|
7146
|
-
import { join as
|
|
7454
|
+
import { join as join17 } from "node:path";
|
|
7147
7455
|
import {
|
|
7148
7456
|
closeCaptureSession as closeCaptureSession9,
|
|
7149
7457
|
createCaptureSession as createCaptureSession9,
|
|
@@ -7153,7 +7461,7 @@ import {
|
|
|
7153
7461
|
|
|
7154
7462
|
// src/services/distributed/chunkFrames.ts
|
|
7155
7463
|
import { existsSync as existsSync12, readdirSync as readdirSync4 } from "node:fs";
|
|
7156
|
-
import { extname as extname2, join as
|
|
7464
|
+
import { extname as extname2, join as join16 } from "node:path";
|
|
7157
7465
|
import {
|
|
7158
7466
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
7159
7467
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -7163,8 +7471,8 @@ import {
|
|
|
7163
7471
|
|
|
7164
7472
|
// src/services/distributed/shared.ts
|
|
7165
7473
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7166
|
-
import { dirname as
|
|
7167
|
-
import { existsSync as existsSync11, readFileSync as
|
|
7474
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
7475
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
|
|
7168
7476
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7169
7477
|
import { promisify as promisify2 } from "node:util";
|
|
7170
7478
|
import {
|
|
@@ -7179,7 +7487,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7179
7487
|
}
|
|
7180
7488
|
function resolvePlanAudioPath(planDir) {
|
|
7181
7489
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7182
|
-
const candidate =
|
|
7490
|
+
const candidate = join15(planDir, name);
|
|
7183
7491
|
if (existsSync11(candidate)) return candidate;
|
|
7184
7492
|
}
|
|
7185
7493
|
return null;
|
|
@@ -7436,13 +7744,13 @@ function buildSyntheticRenderJob(input) {
|
|
|
7436
7744
|
var cachedProducerVersion = null;
|
|
7437
7745
|
function readProducerVersion() {
|
|
7438
7746
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
7439
|
-
const startDir =
|
|
7747
|
+
const startDir = dirname9(fileURLToPath3(import.meta.url));
|
|
7440
7748
|
let current = startDir;
|
|
7441
7749
|
for (let i = 0; i < 10; i++) {
|
|
7442
|
-
const candidate =
|
|
7750
|
+
const candidate = join15(current, "package.json");
|
|
7443
7751
|
if (existsSync11(candidate)) {
|
|
7444
7752
|
try {
|
|
7445
|
-
const pkg = JSON.parse(
|
|
7753
|
+
const pkg = JSON.parse(readFileSync7(candidate, "utf-8"));
|
|
7446
7754
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
7447
7755
|
cachedProducerVersion = pkg.version;
|
|
7448
7756
|
return pkg.version;
|
|
@@ -7450,7 +7758,7 @@ function readProducerVersion() {
|
|
|
7450
7758
|
} catch {
|
|
7451
7759
|
}
|
|
7452
7760
|
}
|
|
7453
|
-
const parent =
|
|
7761
|
+
const parent = dirname9(current);
|
|
7454
7762
|
if (parent === current) break;
|
|
7455
7763
|
current = parent;
|
|
7456
7764
|
}
|
|
@@ -7493,7 +7801,7 @@ function frameNumberFromFileName(name) {
|
|
|
7493
7801
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
7494
7802
|
const result = [];
|
|
7495
7803
|
for (const v of videos) {
|
|
7496
|
-
const outputDir =
|
|
7804
|
+
const outputDir = join16(planDir, "video-frames", v.videoId);
|
|
7497
7805
|
if (!existsSync12(outputDir)) {
|
|
7498
7806
|
throw new Error(
|
|
7499
7807
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
@@ -7512,7 +7820,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
7512
7820
|
if (!frameName) continue;
|
|
7513
7821
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
7514
7822
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
7515
|
-
framePaths.set(frameIndex,
|
|
7823
|
+
framePaths.set(frameIndex, join16(outputDir, frameName));
|
|
7516
7824
|
}
|
|
7517
7825
|
result.push({
|
|
7518
7826
|
videoId: v.videoId,
|
|
@@ -7602,14 +7910,14 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
7602
7910
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
7603
7911
|
return Promise.all(
|
|
7604
7912
|
deferred.map(async (video) => {
|
|
7605
|
-
const outputDir =
|
|
7913
|
+
const outputDir = join16(input.outputRoot, video.videoId);
|
|
7606
7914
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
7607
7915
|
if (indexes.length === 0) {
|
|
7608
7916
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
7609
7917
|
}
|
|
7610
7918
|
const range = video.deferredRange;
|
|
7611
7919
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
7612
|
-
const sourcePath = remoteUrl ??
|
|
7920
|
+
const sourcePath = remoteUrl ?? join16(input.planDir, ...range.sourcePath.split("/"));
|
|
7613
7921
|
if (remoteUrl === void 0 && !existsSync12(sourcePath)) {
|
|
7614
7922
|
throw new RenderChunkValidationError(
|
|
7615
7923
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -7658,12 +7966,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7658
7966
|
planVideos,
|
|
7659
7967
|
frames: sampled,
|
|
7660
7968
|
fps: fpsRatio,
|
|
7661
|
-
outputRoot:
|
|
7969
|
+
outputRoot: join17(workDir, "frame-cost-video-frames"),
|
|
7662
7970
|
cfg: input.cfg
|
|
7663
7971
|
})
|
|
7664
7972
|
];
|
|
7665
7973
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
7666
|
-
const compiledDir =
|
|
7974
|
+
const compiledDir = join17(planDir, "compiled");
|
|
7667
7975
|
const fileServer = await createFileServer({
|
|
7668
7976
|
projectDir: compiledDir,
|
|
7669
7977
|
compiledDir,
|
|
@@ -7675,7 +7983,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7675
7983
|
try {
|
|
7676
7984
|
session = await createCaptureSession9(
|
|
7677
7985
|
fileServer.url,
|
|
7678
|
-
|
|
7986
|
+
join17(workDir, "frame-cost"),
|
|
7679
7987
|
buildChunkCaptureOptions({
|
|
7680
7988
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
7681
7989
|
encoder: input,
|
|
@@ -7772,8 +8080,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
7772
8080
|
}
|
|
7773
8081
|
|
|
7774
8082
|
// src/services/render/stages/freezePlan.ts
|
|
7775
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
7776
|
-
import { join as
|
|
8083
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8084
|
+
import { join as join18, relative as relative3, resolve as resolve8 } from "node:path";
|
|
7777
8085
|
|
|
7778
8086
|
// src/services/distributed/planProtocol.ts
|
|
7779
8087
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -7871,12 +8179,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
|
|
|
7871
8179
|
}
|
|
7872
8180
|
|
|
7873
8181
|
// src/services/render/stages/planHash.ts
|
|
7874
|
-
import { createHash as
|
|
7875
|
-
import { closeSync as
|
|
8182
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
8183
|
+
import { closeSync as closeSync3, createReadStream as createReadStream3, openSync as openSync3, readSync as readSync2 } from "node:fs";
|
|
7876
8184
|
var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
|
|
7877
8185
|
var FIELD_DELIMITER = Buffer.from([0]);
|
|
7878
8186
|
function computePlanHash(input) {
|
|
7879
|
-
const hash =
|
|
8187
|
+
const hash = createHash5("sha256");
|
|
7880
8188
|
hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
|
|
7881
8189
|
hash.update(input.compositionHtml);
|
|
7882
8190
|
hash.update(FIELD_DELIMITER);
|
|
@@ -7923,27 +8231,27 @@ function canonicalJsonStringify(value) {
|
|
|
7923
8231
|
throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
|
|
7924
8232
|
}
|
|
7925
8233
|
function sha256Hex(bytes) {
|
|
7926
|
-
const h =
|
|
8234
|
+
const h = createHash5("sha256");
|
|
7927
8235
|
h.update(bytes);
|
|
7928
8236
|
return h.digest("hex");
|
|
7929
8237
|
}
|
|
7930
8238
|
function sha256File(path) {
|
|
7931
|
-
const hash =
|
|
8239
|
+
const hash = createHash5("sha256");
|
|
7932
8240
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
7933
|
-
const fd =
|
|
8241
|
+
const fd = openSync3(path, "r");
|
|
7934
8242
|
try {
|
|
7935
8243
|
let bytesRead = 0;
|
|
7936
8244
|
do {
|
|
7937
|
-
bytesRead =
|
|
8245
|
+
bytesRead = readSync2(fd, buffer, 0, buffer.byteLength, null);
|
|
7938
8246
|
if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
|
|
7939
8247
|
} while (bytesRead > 0);
|
|
7940
8248
|
} finally {
|
|
7941
|
-
|
|
8249
|
+
closeSync3(fd);
|
|
7942
8250
|
}
|
|
7943
8251
|
return hash.digest("hex");
|
|
7944
8252
|
}
|
|
7945
8253
|
async function sha256FileAsync(path) {
|
|
7946
|
-
const hash =
|
|
8254
|
+
const hash = createHash5("sha256");
|
|
7947
8255
|
for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
|
|
7948
8256
|
hash.update(chunk);
|
|
7949
8257
|
}
|
|
@@ -7987,11 +8295,11 @@ function stripUndefined(value) {
|
|
|
7987
8295
|
}
|
|
7988
8296
|
function listPlanFiles(planDir) {
|
|
7989
8297
|
const results = [];
|
|
7990
|
-
const rootResolved =
|
|
8298
|
+
const rootResolved = resolve8(planDir);
|
|
7991
8299
|
function walk(dir) {
|
|
7992
8300
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
7993
8301
|
for (const entry of entries) {
|
|
7994
|
-
const full =
|
|
8302
|
+
const full = join18(dir, entry.name);
|
|
7995
8303
|
if (entry.isDirectory()) {
|
|
7996
8304
|
walk(full);
|
|
7997
8305
|
} else if (entry.isFile()) {
|
|
@@ -8012,7 +8320,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
8012
8320
|
const assets = [];
|
|
8013
8321
|
for (const file of files) {
|
|
8014
8322
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
8015
|
-
compositionHtml =
|
|
8323
|
+
compositionHtml = readFileSync8(file.absolutePath);
|
|
8016
8324
|
continue;
|
|
8017
8325
|
}
|
|
8018
8326
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -8032,23 +8340,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8032
8340
|
function writePlanRemoteFiles(planDir, files) {
|
|
8033
8341
|
if (files.length === 0) return;
|
|
8034
8342
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8035
|
-
|
|
8343
|
+
writeFileSync5(join18(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8036
8344
|
}
|
|
8037
8345
|
function readPlanRemoteFiles(planDir) {
|
|
8038
|
-
const path =
|
|
8039
|
-
return existsSync13(path) ? JSON.parse(
|
|
8346
|
+
const path = join18(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8347
|
+
return existsSync13(path) ? JSON.parse(readFileSync8(path, "utf-8")) : [];
|
|
8040
8348
|
}
|
|
8041
8349
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8042
|
-
const planJsonPath =
|
|
8043
|
-
const encoderJsonPath =
|
|
8350
|
+
const planJsonPath = join18(planDir, "plan.json");
|
|
8351
|
+
const encoderJsonPath = join18(planDir, "meta", "encoder.json");
|
|
8044
8352
|
if (!existsSync13(planJsonPath)) {
|
|
8045
8353
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8046
8354
|
}
|
|
8047
8355
|
if (!existsSync13(encoderJsonPath)) {
|
|
8048
8356
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8049
8357
|
}
|
|
8050
|
-
const planJson = JSON.parse(
|
|
8051
|
-
const encoderConfigCanonicalJson =
|
|
8358
|
+
const planJson = JSON.parse(readFileSync8(planJsonPath, "utf-8"));
|
|
8359
|
+
const encoderConfigCanonicalJson = readFileSync8(encoderJsonPath, "utf-8");
|
|
8052
8360
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8053
8361
|
return computePlanHash({
|
|
8054
8362
|
compositionHtml,
|
|
@@ -8077,18 +8385,18 @@ async function freezePlan(input) {
|
|
|
8077
8385
|
if (!existsSync13(planDir)) {
|
|
8078
8386
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8079
8387
|
}
|
|
8080
|
-
const metaDir =
|
|
8081
|
-
if (!existsSync13(metaDir))
|
|
8082
|
-
|
|
8083
|
-
|
|
8388
|
+
const metaDir = join18(planDir, "meta");
|
|
8389
|
+
if (!existsSync13(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8390
|
+
writeFileSync5(
|
|
8391
|
+
join18(metaDir, "composition.json"),
|
|
8084
8392
|
`${JSON.stringify(composition, null, 2)}
|
|
8085
8393
|
`,
|
|
8086
8394
|
"utf-8"
|
|
8087
8395
|
);
|
|
8088
8396
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8089
8397
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8090
|
-
|
|
8091
|
-
|
|
8398
|
+
writeFileSync5(join18(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8399
|
+
writeFileSync5(join18(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8092
8400
|
`, "utf-8");
|
|
8093
8401
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8094
8402
|
const planHash = computePlanHash({
|
|
@@ -8112,8 +8420,8 @@ async function freezePlan(input) {
|
|
|
8112
8420
|
duration: durationSeconds,
|
|
8113
8421
|
hasAudio
|
|
8114
8422
|
};
|
|
8115
|
-
const planJsonPath =
|
|
8116
|
-
|
|
8423
|
+
const planJsonPath = join18(planDir, "plan.json");
|
|
8424
|
+
writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8117
8425
|
`, "utf-8");
|
|
8118
8426
|
return { planJsonPath, planHash };
|
|
8119
8427
|
}
|
|
@@ -8154,16 +8462,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8154
8462
|
}
|
|
8155
8463
|
|
|
8156
8464
|
// src/services/distributed/chunkAudio.ts
|
|
8157
|
-
import { closeSync as
|
|
8158
|
-
import { writeFileSync as
|
|
8159
|
-
import { join as
|
|
8160
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as
|
|
8465
|
+
import { closeSync as closeSync4, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
8466
|
+
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8467
|
+
import { join as join20 } from "node:path";
|
|
8468
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8161
8469
|
|
|
8162
8470
|
// src/services/distributed/mp4Stitch.ts
|
|
8163
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as
|
|
8471
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync8 } from "node:fs";
|
|
8164
8472
|
import { open } from "node:fs/promises";
|
|
8165
|
-
import { renameWithRetry as
|
|
8166
|
-
import { dirname as
|
|
8473
|
+
import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
|
|
8474
|
+
import { dirname as dirname10, join as join19 } from "node:path";
|
|
8167
8475
|
import { finished, pipeline } from "node:stream/promises";
|
|
8168
8476
|
function boxAt(src, off, at, limit) {
|
|
8169
8477
|
let size = src.readUInt32BE(off);
|
|
@@ -8176,16 +8484,16 @@ function boxAt(src, off, at, limit) {
|
|
|
8176
8484
|
return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
|
|
8177
8485
|
}
|
|
8178
8486
|
function childBoxes(buf, parent) {
|
|
8179
|
-
const
|
|
8180
|
-
for (let at = parent.bodyStart; at < parent.end; at =
|
|
8181
|
-
|
|
8487
|
+
const boxes2 = [];
|
|
8488
|
+
for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
|
|
8489
|
+
boxes2.push(boxAt(buf, at, at, parent.end));
|
|
8182
8490
|
}
|
|
8183
|
-
return
|
|
8491
|
+
return boxes2;
|
|
8184
8492
|
}
|
|
8185
8493
|
function findChild(buf, parent, type) {
|
|
8186
8494
|
return childBoxes(buf, parent).find((box2) => box2.type === type);
|
|
8187
8495
|
}
|
|
8188
|
-
function
|
|
8496
|
+
function child2(buf, parent, type) {
|
|
8189
8497
|
const found = findChild(buf, parent, type);
|
|
8190
8498
|
if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
|
|
8191
8499
|
return found;
|
|
@@ -8237,7 +8545,7 @@ function rows(buf, table, columns, countAt = 4) {
|
|
|
8237
8545
|
function readMediaTime(moov, trak) {
|
|
8238
8546
|
const edts = findChild(moov, trak, "edts");
|
|
8239
8547
|
if (!edts) return 0;
|
|
8240
|
-
const elst =
|
|
8548
|
+
const elst = child2(moov, edts, "elst");
|
|
8241
8549
|
const wide = moov[elst.bodyStart] === 1;
|
|
8242
8550
|
const entries = moov.readUInt32BE(elst.bodyStart + 4);
|
|
8243
8551
|
for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
|
|
@@ -8252,7 +8560,7 @@ function readSizes(moov, stsz) {
|
|
|
8252
8560
|
if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
|
|
8253
8561
|
return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
|
|
8254
8562
|
}
|
|
8255
|
-
function
|
|
8563
|
+
function readSync3(moov, stbl, sampleCount) {
|
|
8256
8564
|
const stss = findChild(moov, stbl, "stss");
|
|
8257
8565
|
if (!stss) return null;
|
|
8258
8566
|
const sync = Array(sampleCount).fill(false);
|
|
@@ -8262,10 +8570,10 @@ function readSync2(moov, stbl, sampleCount) {
|
|
|
8262
8570
|
function readChunkOffsets(moov, stbl) {
|
|
8263
8571
|
const stco = findChild(moov, stbl, "stco");
|
|
8264
8572
|
if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
|
|
8265
|
-
return rows(moov,
|
|
8573
|
+
return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
|
|
8266
8574
|
}
|
|
8267
8575
|
function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
8268
|
-
const stsc = rows(moov,
|
|
8576
|
+
const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
|
|
8269
8577
|
return stsc.flatMap(([firstChunk, perChunk, description], i) => {
|
|
8270
8578
|
if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
|
|
8271
8579
|
const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
|
|
@@ -8273,28 +8581,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
|
|
|
8273
8581
|
});
|
|
8274
8582
|
}
|
|
8275
8583
|
function readTrack(moov, trak) {
|
|
8276
|
-
const mdia =
|
|
8277
|
-
const hdlr =
|
|
8278
|
-
const stbl =
|
|
8584
|
+
const mdia = child2(moov, trak, "mdia");
|
|
8585
|
+
const hdlr = child2(moov, mdia, "hdlr");
|
|
8586
|
+
const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
|
|
8279
8587
|
if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
|
|
8280
|
-
const stts = rows(moov,
|
|
8588
|
+
const stts = rows(moov, child2(moov, stbl, "stts"), 2);
|
|
8281
8589
|
const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
|
|
8282
|
-
const sizes = readSizes(moov,
|
|
8590
|
+
const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
|
|
8283
8591
|
const chunkOffsets = readChunkOffsets(moov, stbl);
|
|
8284
8592
|
const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
|
|
8285
8593
|
if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
|
|
8286
8594
|
throw new Error("[mp4Stitch] sample tables disagree on the sample count");
|
|
8287
8595
|
}
|
|
8288
|
-
const stsd =
|
|
8596
|
+
const stsd = child2(moov, stbl, "stsd");
|
|
8289
8597
|
return {
|
|
8290
8598
|
trak,
|
|
8291
8599
|
handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
|
|
8292
|
-
timescale: timescaleOf(moov,
|
|
8600
|
+
timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
|
|
8293
8601
|
mediaTime: readMediaTime(moov, trak),
|
|
8294
8602
|
stsd: moov.subarray(stsd.start, stsd.end),
|
|
8295
8603
|
durations,
|
|
8296
8604
|
sizes,
|
|
8297
|
-
sync:
|
|
8605
|
+
sync: readSync3(moov, stbl, sizes.length),
|
|
8298
8606
|
samplesPerChunk,
|
|
8299
8607
|
chunkOffsets
|
|
8300
8608
|
};
|
|
@@ -8435,9 +8743,9 @@ function buildStitchedHeader(layouts, audio) {
|
|
|
8435
8743
|
}
|
|
8436
8744
|
const template = layouts[0].moov;
|
|
8437
8745
|
const root = boxAt(template, 0, 0, template.length);
|
|
8438
|
-
const mvhd =
|
|
8746
|
+
const mvhd = child2(template, root, "mvhd");
|
|
8439
8747
|
const movieTimescale = timescaleOf(template, mvhd);
|
|
8440
|
-
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template,
|
|
8748
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
|
|
8441
8749
|
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
8442
8750
|
const buildMoov = (dataStart2) => {
|
|
8443
8751
|
const traks = plan2.map((track, t) => {
|
|
@@ -8527,18 +8835,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
8527
8835
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
8528
8836
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
8529
8837
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
8530
|
-
|
|
8838
|
+
mkdirSync8(dirname10(outputPath), { recursive: true });
|
|
8531
8839
|
const workDir = mkdtempSync2(`${outputPath}.stitch-`);
|
|
8532
|
-
const out = createWriteStream(
|
|
8840
|
+
const out = createWriteStream(join19(workDir, "out.mp4"));
|
|
8533
8841
|
try {
|
|
8534
8842
|
out.write(header);
|
|
8535
8843
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
8536
8844
|
out.end();
|
|
8537
8845
|
await finished(out);
|
|
8538
|
-
|
|
8846
|
+
renameWithRetry6(join19(workDir, "out.mp4"), outputPath);
|
|
8539
8847
|
} finally {
|
|
8540
8848
|
out.destroy();
|
|
8541
|
-
|
|
8849
|
+
rmSync8(workDir, { recursive: true, force: true });
|
|
8542
8850
|
}
|
|
8543
8851
|
}
|
|
8544
8852
|
|
|
@@ -8627,9 +8935,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
8627
8935
|
offset += track.sizes[offsets.length - 1];
|
|
8628
8936
|
}
|
|
8629
8937
|
});
|
|
8630
|
-
const bytes =
|
|
8938
|
+
const bytes = readFileSync9(input.audioPath);
|
|
8631
8939
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
8632
|
-
|
|
8940
|
+
mkdirSync9(join20(input.planDir, SLICE_DIR), { recursive: true });
|
|
8633
8941
|
input.chunks.forEach((_, index) => {
|
|
8634
8942
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
8635
8943
|
if (!cut) return;
|
|
@@ -8637,18 +8945,18 @@ async function writeChunkAudioSlices(input) {
|
|
|
8637
8945
|
const start = offsets[cut.firstPacket];
|
|
8638
8946
|
layout.partStarts[index] = start;
|
|
8639
8947
|
const end = offsets[last] + track.sizes[last];
|
|
8640
|
-
|
|
8948
|
+
writeFileSync6(join20(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
8641
8949
|
});
|
|
8642
8950
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
8643
|
-
|
|
8644
|
-
|
|
8951
|
+
writeFileSync6(join20(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
8952
|
+
writeFileSync6(join20(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
8645
8953
|
}
|
|
8646
8954
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
8647
|
-
const layoutPath =
|
|
8955
|
+
const layoutPath = join20(planDir, SLICE_LAYOUT);
|
|
8648
8956
|
if (!existsSync14(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
8649
|
-
const layout = JSON.parse(
|
|
8650
|
-
const frame =
|
|
8651
|
-
const fd =
|
|
8957
|
+
const layout = JSON.parse(readFileSync9(layoutPath, "utf-8"));
|
|
8958
|
+
const frame = readFileSync9(join20(planDir, SLICE_FRAME));
|
|
8959
|
+
const fd = openSync4(scratchPath, "w");
|
|
8652
8960
|
try {
|
|
8653
8961
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
8654
8962
|
writeSync(
|
|
@@ -8660,12 +8968,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
8660
8968
|
);
|
|
8661
8969
|
const start = layout.partStarts[chunkIndex];
|
|
8662
8970
|
if (start !== void 0) {
|
|
8663
|
-
const part =
|
|
8971
|
+
const part = readFileSync9(join20(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
8664
8972
|
writeSync(fd, part, 0, part.length, start);
|
|
8665
8973
|
}
|
|
8666
8974
|
ftruncateSync(fd, layout.sizeBytes);
|
|
8667
8975
|
} finally {
|
|
8668
|
-
|
|
8976
|
+
closeSync4(fd);
|
|
8669
8977
|
}
|
|
8670
8978
|
return scratchPath;
|
|
8671
8979
|
}
|
|
@@ -8685,7 +8993,7 @@ async function muxChunkWithAudio(input) {
|
|
|
8685
8993
|
const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
|
|
8686
8994
|
appendRenderProvenanceArgs(args, input.outputPath);
|
|
8687
8995
|
args.push("-y", input.outputPath);
|
|
8688
|
-
const result = await
|
|
8996
|
+
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
8689
8997
|
if (!result.success) {
|
|
8690
8998
|
throw new Error(
|
|
8691
8999
|
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
|
|
@@ -8714,9 +9022,9 @@ async function muxChunkWithAudio(input) {
|
|
|
8714
9022
|
}
|
|
8715
9023
|
|
|
8716
9024
|
// src/services/distributed/planSize.ts
|
|
8717
|
-
import { createHash as
|
|
9025
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
8718
9026
|
import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
|
|
8719
|
-
import { extname as extname3, join as
|
|
9027
|
+
import { extname as extname3, join as join21, relative as relative4 } from "node:path";
|
|
8720
9028
|
var MAX_TOP_COMPONENTS = 10;
|
|
8721
9029
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8722
9030
|
".aac",
|
|
@@ -8744,7 +9052,7 @@ var PLAN_ROOT_CATEGORY = {
|
|
|
8744
9052
|
"plan.json": "metadata"
|
|
8745
9053
|
};
|
|
8746
9054
|
function hashComponent(value) {
|
|
8747
|
-
return
|
|
9055
|
+
return createHash6("sha256").update(value).digest("hex").slice(0, 12);
|
|
8748
9056
|
}
|
|
8749
9057
|
function classifyFile(segments, rootKind) {
|
|
8750
9058
|
const rootName = segments[0] ?? "";
|
|
@@ -8828,7 +9136,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
8828
9136
|
return;
|
|
8829
9137
|
}
|
|
8830
9138
|
for (const entry of entries) {
|
|
8831
|
-
const full =
|
|
9139
|
+
const full = join21(dir, entry.name);
|
|
8832
9140
|
if (entry.isDirectory()) {
|
|
8833
9141
|
walkRegularFiles(full, visit);
|
|
8834
9142
|
} else if (entry.isFile()) {
|
|
@@ -9044,13 +9352,13 @@ function assertPositiveInteger(name, value) {
|
|
|
9044
9352
|
}
|
|
9045
9353
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
9046
9354
|
"plan.json",
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9355
|
+
join22("meta", "composition.json"),
|
|
9356
|
+
join22("meta", "encoder.json"),
|
|
9357
|
+
join22("meta", "chunks.json")
|
|
9050
9358
|
];
|
|
9051
9359
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
9052
9360
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
9053
|
-
|
|
9361
|
+
rmSync9(join22(planDir, relativePath), { force: true });
|
|
9054
9362
|
}
|
|
9055
9363
|
}
|
|
9056
9364
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -9195,23 +9503,23 @@ function createVideoSourceShipper(input) {
|
|
|
9195
9503
|
const known = shipped.get(srcPath);
|
|
9196
9504
|
if (known) return known;
|
|
9197
9505
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
9198
|
-
const current = stagedRel === null ? srcPath :
|
|
9506
|
+
const current = stagedRel === null ? srcPath : join22(input.planDir, "video-frames", stagedRel);
|
|
9199
9507
|
let result = null;
|
|
9200
9508
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9201
9509
|
const rel = pathInside(root, srcPath);
|
|
9202
|
-
if (rel !== null && (existsSync15(
|
|
9510
|
+
if (rel !== null && (existsSync15(join22(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9203
9511
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9204
9512
|
break;
|
|
9205
9513
|
}
|
|
9206
9514
|
}
|
|
9207
9515
|
if (result === null) {
|
|
9208
9516
|
result = `video-sources/${shipped.size}${extname4(current)}`;
|
|
9209
|
-
const destination =
|
|
9210
|
-
|
|
9517
|
+
const destination = join22(input.planDir, ...result.split("/"));
|
|
9518
|
+
mkdirSync10(join22(input.planDir, "video-sources"), { recursive: true });
|
|
9211
9519
|
try {
|
|
9212
9520
|
linkSync(current, destination);
|
|
9213
9521
|
} catch {
|
|
9214
|
-
|
|
9522
|
+
copyFileSync6(current, destination);
|
|
9215
9523
|
}
|
|
9216
9524
|
}
|
|
9217
9525
|
shipped.set(srcPath, result);
|
|
@@ -9227,7 +9535,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9227
9535
|
useGpu: false,
|
|
9228
9536
|
browserGpuMode: "software"
|
|
9229
9537
|
});
|
|
9230
|
-
if (!existsSync15(planDir))
|
|
9538
|
+
if (!existsSync15(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9231
9539
|
const log = config.logger ?? defaultLogger;
|
|
9232
9540
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9233
9541
|
const abortSignal = config.abortSignal;
|
|
@@ -9256,15 +9564,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9256
9564
|
variables: config.variables
|
|
9257
9565
|
});
|
|
9258
9566
|
const entryFile = config.entryFile ?? "index.html";
|
|
9259
|
-
const htmlPath =
|
|
9567
|
+
const htmlPath = join22(projectDir, entryFile);
|
|
9260
9568
|
if (!existsSync15(htmlPath)) {
|
|
9261
9569
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9262
9570
|
}
|
|
9263
|
-
const workDir =
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
const compiledDir =
|
|
9267
|
-
|
|
9571
|
+
const workDir = join22(planDir, ".plan-work");
|
|
9572
|
+
rmSync9(workDir, { recursive: true, force: true });
|
|
9573
|
+
mkdirSync10(workDir, { recursive: true });
|
|
9574
|
+
const compiledDir = join22(workDir, "compiled");
|
|
9575
|
+
mkdirSync10(compiledDir, { recursive: true });
|
|
9268
9576
|
let stageStarted = Date.now();
|
|
9269
9577
|
cpSync2(projectDir, compiledDir, {
|
|
9270
9578
|
recursive: true,
|
|
@@ -9277,10 +9585,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9277
9585
|
}
|
|
9278
9586
|
});
|
|
9279
9587
|
logPlanStage(log, "copy", stageStarted);
|
|
9280
|
-
const finalCompiledDir =
|
|
9588
|
+
const finalCompiledDir = join22(planDir, "compiled");
|
|
9281
9589
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
9282
9590
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
9283
|
-
const remoteLocalPath = (rel) =>
|
|
9591
|
+
const remoteLocalPath = (rel) => join22(projectDir, ...rel.split("/"));
|
|
9284
9592
|
for (const [, file] of remoteFiles) {
|
|
9285
9593
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
9286
9594
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -9466,18 +9774,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9466
9774
|
let stageStarted = Date.now();
|
|
9467
9775
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
9468
9776
|
logPlanStage(log, "extract", stageStarted);
|
|
9469
|
-
const stagedVideoFrames =
|
|
9470
|
-
const videoFramesDst =
|
|
9471
|
-
if (existsSync15(videoFramesDst))
|
|
9472
|
-
const videoSourcesDir =
|
|
9473
|
-
|
|
9777
|
+
const stagedVideoFrames = join22(compiledDir, "__hyperframes_video_frames");
|
|
9778
|
+
const videoFramesDst = join22(planDir, "video-frames");
|
|
9779
|
+
if (existsSync15(videoFramesDst)) rmSync9(videoFramesDst, { recursive: true, force: true });
|
|
9780
|
+
const videoSourcesDir = join22(planDir, "video-sources");
|
|
9781
|
+
rmSync9(videoSourcesDir, { recursive: true, force: true });
|
|
9474
9782
|
if (existsSync15(stagedVideoFrames)) {
|
|
9475
|
-
|
|
9783
|
+
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
9476
9784
|
} else {
|
|
9477
|
-
|
|
9785
|
+
mkdirSync10(videoFramesDst, { recursive: true });
|
|
9478
9786
|
}
|
|
9479
|
-
if (existsSync15(finalCompiledDir))
|
|
9480
|
-
|
|
9787
|
+
if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
|
|
9788
|
+
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
9481
9789
|
const shipVideoSource = createVideoSourceShipper({
|
|
9482
9790
|
planDir,
|
|
9483
9791
|
projectDir,
|
|
@@ -9506,7 +9814,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9506
9814
|
};
|
|
9507
9815
|
})
|
|
9508
9816
|
});
|
|
9509
|
-
|
|
9817
|
+
mkdirSync10(join22(planDir, "meta"), { recursive: true });
|
|
9510
9818
|
writePlanRemoteFiles(
|
|
9511
9819
|
planDir,
|
|
9512
9820
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -9515,8 +9823,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9515
9823
|
sizeBytes
|
|
9516
9824
|
}))
|
|
9517
9825
|
);
|
|
9518
|
-
|
|
9519
|
-
|
|
9826
|
+
writeFileSync7(
|
|
9827
|
+
join22(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
9520
9828
|
JSON.stringify(planVideosJson, null, 2),
|
|
9521
9829
|
"utf-8"
|
|
9522
9830
|
);
|
|
@@ -9595,9 +9903,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9595
9903
|
if (audioResult?.audioError) {
|
|
9596
9904
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
9597
9905
|
}
|
|
9598
|
-
const planAudioPath =
|
|
9906
|
+
const planAudioPath = join22(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
9599
9907
|
if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
|
|
9600
|
-
|
|
9908
|
+
renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
|
|
9601
9909
|
}
|
|
9602
9910
|
if (chunksCarryAudio && existsSync15(planAudioPath)) {
|
|
9603
9911
|
if (options.sliceChunkAudio) {
|
|
@@ -9644,7 +9952,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9644
9952
|
format: config.format
|
|
9645
9953
|
};
|
|
9646
9954
|
try {
|
|
9647
|
-
|
|
9955
|
+
rmSync9(workDir, { recursive: true, force: true });
|
|
9648
9956
|
} catch (err) {
|
|
9649
9957
|
log.warn("[plan] failed to remove temp work dir", {
|
|
9650
9958
|
workDir,
|
|
@@ -9711,10 +10019,10 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
9711
10019
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
9712
10020
|
}
|
|
9713
10021
|
if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
|
|
9714
|
-
|
|
10022
|
+
renameWithRetry7(mixed.audioOutputPath, outputPath);
|
|
9715
10023
|
return { hasAudio: true };
|
|
9716
10024
|
} finally {
|
|
9717
|
-
|
|
10025
|
+
rmSync9(c.workDir, { recursive: true, force: true });
|
|
9718
10026
|
}
|
|
9719
10027
|
}
|
|
9720
10028
|
async function plan(projectDir, config, planDir, options = {}) {
|
|
@@ -9736,35 +10044,35 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
9736
10044
|
|
|
9737
10045
|
// src/services/distributed/planV2.ts
|
|
9738
10046
|
import {
|
|
9739
|
-
copyFileSync as
|
|
10047
|
+
copyFileSync as copyFileSync8,
|
|
9740
10048
|
existsSync as existsSync17,
|
|
9741
10049
|
lstatSync as lstatSync2,
|
|
9742
|
-
mkdirSync as
|
|
10050
|
+
mkdirSync as mkdirSync12,
|
|
9743
10051
|
mkdtempSync as mkdtempSync4,
|
|
9744
|
-
readFileSync as
|
|
10052
|
+
readFileSync as readFileSync10,
|
|
9745
10053
|
readdirSync as readdirSync7,
|
|
9746
|
-
rmSync as
|
|
9747
|
-
statSync as
|
|
9748
|
-
writeFileSync as
|
|
10054
|
+
rmSync as rmSync11,
|
|
10055
|
+
statSync as statSync7,
|
|
10056
|
+
writeFileSync as writeFileSync9
|
|
9749
10057
|
} from "node:fs";
|
|
9750
10058
|
import { tmpdir } from "node:os";
|
|
9751
|
-
import { dirname as
|
|
10059
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join25, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
9752
10060
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
9753
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
10061
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
9754
10062
|
|
|
9755
10063
|
// src/services/distributed/planV2Publisher.ts
|
|
9756
10064
|
import {
|
|
9757
|
-
copyFileSync as
|
|
10065
|
+
copyFileSync as copyFileSync7,
|
|
9758
10066
|
existsSync as existsSync16,
|
|
9759
10067
|
linkSync as linkSync2,
|
|
9760
|
-
mkdirSync as
|
|
10068
|
+
mkdirSync as mkdirSync11,
|
|
9761
10069
|
mkdtempSync as mkdtempSync3,
|
|
9762
|
-
rmSync as
|
|
9763
|
-
statSync as
|
|
9764
|
-
writeFileSync as
|
|
10070
|
+
rmSync as rmSync10,
|
|
10071
|
+
statSync as statSync6,
|
|
10072
|
+
writeFileSync as writeFileSync8
|
|
9765
10073
|
} from "node:fs";
|
|
9766
|
-
import { renameWithRetry as
|
|
9767
|
-
import { dirname as
|
|
10074
|
+
import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
|
|
10075
|
+
import { dirname as dirname11, join as join24 } from "node:path";
|
|
9768
10076
|
|
|
9769
10077
|
// src/services/distributed/planV2Errors.ts
|
|
9770
10078
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -9779,7 +10087,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
9779
10087
|
};
|
|
9780
10088
|
|
|
9781
10089
|
// src/services/distributed/planV2Layout.ts
|
|
9782
|
-
import { join as
|
|
10090
|
+
import { join as join23 } from "node:path";
|
|
9783
10091
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
9784
10092
|
function assertPlanV2Sha256(value, field) {
|
|
9785
10093
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -9789,7 +10097,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
9789
10097
|
}
|
|
9790
10098
|
function planV2BlobPath(root, sha256) {
|
|
9791
10099
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
9792
|
-
return
|
|
10100
|
+
return join23(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
9793
10101
|
}
|
|
9794
10102
|
|
|
9795
10103
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -9829,12 +10137,12 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9829
10137
|
}
|
|
9830
10138
|
this.destinationDir = destinationDir;
|
|
9831
10139
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
9832
|
-
|
|
9833
|
-
this.temporaryDir = mkdtempSync3(
|
|
10140
|
+
mkdirSync11(dirname11(destinationDir), { recursive: true });
|
|
10141
|
+
this.temporaryDir = mkdtempSync3(join24(dirname11(destinationDir), ".plan-v2-publish-"));
|
|
9834
10142
|
}
|
|
9835
10143
|
async putBlob(blob) {
|
|
9836
10144
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
9837
|
-
const sourceSize =
|
|
10145
|
+
const sourceSize = statSync6(blob.sourcePath).size;
|
|
9838
10146
|
if (sourceSize !== blob.sizeBytes) {
|
|
9839
10147
|
throw new PlanV2IntegrityError(
|
|
9840
10148
|
`published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
|
|
@@ -9842,19 +10150,19 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9842
10150
|
}
|
|
9843
10151
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
9844
10152
|
if (existsSync16(destinationPath)) return;
|
|
9845
|
-
|
|
9846
|
-
const stagingDir = mkdtempSync3(
|
|
9847
|
-
const temporaryPath =
|
|
10153
|
+
mkdirSync11(dirname11(destinationPath), { recursive: true });
|
|
10154
|
+
const stagingDir = mkdtempSync3(join24(dirname11(destinationPath), ".plan-v2-blob-"));
|
|
10155
|
+
const temporaryPath = join24(stagingDir, "blob");
|
|
9848
10156
|
try {
|
|
9849
10157
|
try {
|
|
9850
10158
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
9851
10159
|
} catch (error) {
|
|
9852
10160
|
if (!canFallbackToCopy(error)) throw error;
|
|
9853
|
-
|
|
10161
|
+
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
9854
10162
|
}
|
|
9855
|
-
|
|
10163
|
+
renameWithRetry8(temporaryPath, destinationPath);
|
|
9856
10164
|
} finally {
|
|
9857
|
-
|
|
10165
|
+
rmSync10(stagingDir, { recursive: true, force: true });
|
|
9858
10166
|
}
|
|
9859
10167
|
}
|
|
9860
10168
|
async commitManifest(manifestBytes) {
|
|
@@ -9865,13 +10173,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
9865
10173
|
);
|
|
9866
10174
|
}
|
|
9867
10175
|
}
|
|
9868
|
-
|
|
9869
|
-
|
|
10176
|
+
writeFileSync8(join24(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10177
|
+
renameWithRetry8(this.temporaryDir, this.destinationDir);
|
|
9870
10178
|
this.#committed = true;
|
|
9871
10179
|
}
|
|
9872
10180
|
async abort() {
|
|
9873
10181
|
if (!this.#committed) {
|
|
9874
|
-
|
|
10182
|
+
rmSync10(this.temporaryDir, { recursive: true, force: true });
|
|
9875
10183
|
}
|
|
9876
10184
|
}
|
|
9877
10185
|
};
|
|
@@ -9890,7 +10198,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
9890
10198
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
9891
10199
|
}
|
|
9892
10200
|
function readJsonFile(path, label) {
|
|
9893
|
-
const contents =
|
|
10201
|
+
const contents = readFileSync10(path, "utf-8");
|
|
9894
10202
|
try {
|
|
9895
10203
|
const parsed = JSON.parse(contents);
|
|
9896
10204
|
return parsed;
|
|
@@ -9907,10 +10215,10 @@ function assertSafeRelativePath(path) {
|
|
|
9907
10215
|
}
|
|
9908
10216
|
function listFiles(root) {
|
|
9909
10217
|
const files = [];
|
|
9910
|
-
const rootResolved =
|
|
10218
|
+
const rootResolved = resolve9(root);
|
|
9911
10219
|
function walk(dir) {
|
|
9912
10220
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
9913
|
-
const absolutePath =
|
|
10221
|
+
const absolutePath = join25(dir, entry.name);
|
|
9914
10222
|
if (entry.isDirectory()) {
|
|
9915
10223
|
walk(absolutePath);
|
|
9916
10224
|
} else if (entry.isFile()) {
|
|
@@ -9937,13 +10245,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
9937
10245
|
}
|
|
9938
10246
|
}
|
|
9939
10247
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
9940
|
-
const videoRoot =
|
|
10248
|
+
const videoRoot = join25(executionPlanDir, "video-frames");
|
|
9941
10249
|
if (!existsSync17(videoRoot)) return;
|
|
9942
10250
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
9943
10251
|
if (!videoEntry.isDirectory()) continue;
|
|
9944
|
-
const videoDir =
|
|
10252
|
+
const videoDir = join25(videoRoot, videoEntry.name);
|
|
9945
10253
|
if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
9946
|
-
const sentinelPath =
|
|
10254
|
+
const sentinelPath = join25(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
9947
10255
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
9948
10256
|
}
|
|
9949
10257
|
}
|
|
@@ -9953,8 +10261,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
9953
10261
|
return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
|
|
9954
10262
|
}
|
|
9955
10263
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
9956
|
-
const videoRoot =
|
|
9957
|
-
const outputDir =
|
|
10264
|
+
const videoRoot = resolve9(planDir, "video-frames");
|
|
10265
|
+
const outputDir = resolve9(videoRoot, videoId);
|
|
9958
10266
|
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep3}`)) {
|
|
9959
10267
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
9960
10268
|
}
|
|
@@ -9990,7 +10298,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
9990
10298
|
if (video.deferredRange) {
|
|
9991
10299
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
9992
10300
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
9993
|
-
framePaths2.set(index,
|
|
10301
|
+
framePaths2.set(index, join25(outputDir, String(index)));
|
|
9994
10302
|
}
|
|
9995
10303
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
9996
10304
|
}
|
|
@@ -9998,7 +10306,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
9998
10306
|
const framePaths = /* @__PURE__ */ new Map();
|
|
9999
10307
|
for (const frameName of frameNames) {
|
|
10000
10308
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
10001
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
10309
|
+
assertValidExtractionCacheCompleteSentinel(join25(outputDir, frameName));
|
|
10002
10310
|
continue;
|
|
10003
10311
|
}
|
|
10004
10312
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -10017,7 +10325,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10017
10325
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
10018
10326
|
);
|
|
10019
10327
|
}
|
|
10020
|
-
framePaths.set(frameIndex,
|
|
10328
|
+
framePaths.set(frameIndex, join25(outputDir, frameName));
|
|
10021
10329
|
}
|
|
10022
10330
|
return {
|
|
10023
10331
|
...video,
|
|
@@ -10038,11 +10346,11 @@ function parsePlanVideosJson2(value) {
|
|
|
10038
10346
|
}
|
|
10039
10347
|
}
|
|
10040
10348
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10041
|
-
const videosPath =
|
|
10349
|
+
const videosPath = join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10042
10350
|
if (!existsSync17(videosPath)) return;
|
|
10043
10351
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10044
10352
|
for (const video of videos.extracted) {
|
|
10045
|
-
|
|
10353
|
+
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
10046
10354
|
}
|
|
10047
10355
|
}
|
|
10048
10356
|
function parseChunkSlices(value) {
|
|
@@ -10072,13 +10380,13 @@ function parseChunkSlices(value) {
|
|
|
10072
10380
|
});
|
|
10073
10381
|
}
|
|
10074
10382
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10075
|
-
const videoRoot =
|
|
10383
|
+
const videoRoot = join25(executionPlanDir, "video-frames");
|
|
10076
10384
|
const hasExtractedFrames = existsSync17(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10077
|
-
const videosPath =
|
|
10385
|
+
const videosPath = join25(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10078
10386
|
if (!existsSync17(videosPath)) {
|
|
10079
10387
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10080
10388
|
}
|
|
10081
|
-
const chunksPath =
|
|
10389
|
+
const chunksPath = join25(executionPlanDir, "meta", "chunks.json");
|
|
10082
10390
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10083
10391
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
10084
10392
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -10115,7 +10423,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
10115
10423
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
10116
10424
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
10117
10425
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
10118
|
-
const path = input.deferredSources.get(videoId) ?? relative6(
|
|
10426
|
+
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep3).join("/");
|
|
10119
10427
|
assertSafeRelativePath(path);
|
|
10120
10428
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
10121
10429
|
indexes.add(chunk.index);
|
|
@@ -10133,21 +10441,21 @@ function computeManifestHash(manifest) {
|
|
|
10133
10441
|
}
|
|
10134
10442
|
function writeBlob(sourcePath, destinationPath) {
|
|
10135
10443
|
if (existsSync17(destinationPath)) return;
|
|
10136
|
-
|
|
10137
|
-
const temporaryDir = mkdtempSync4(
|
|
10138
|
-
const temporaryPath =
|
|
10444
|
+
mkdirSync12(dirname12(destinationPath), { recursive: true });
|
|
10445
|
+
const temporaryDir = mkdtempSync4(join25(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10446
|
+
const temporaryPath = join25(temporaryDir, "blob");
|
|
10139
10447
|
try {
|
|
10140
|
-
|
|
10141
|
-
|
|
10448
|
+
copyFileSync8(sourcePath, temporaryPath);
|
|
10449
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
10142
10450
|
} finally {
|
|
10143
|
-
|
|
10451
|
+
rmSync11(temporaryDir, { recursive: true, force: true });
|
|
10144
10452
|
}
|
|
10145
10453
|
}
|
|
10146
10454
|
function publishBlobFor(absolutePath, digests) {
|
|
10147
10455
|
return {
|
|
10148
10456
|
sourcePath: absolutePath,
|
|
10149
10457
|
sha256: planFileSha256(absolutePath, digests),
|
|
10150
|
-
sizeBytes:
|
|
10458
|
+
sizeBytes: statSync7(absolutePath).size
|
|
10151
10459
|
};
|
|
10152
10460
|
}
|
|
10153
10461
|
async function putBlobs(publisher, blobs) {
|
|
@@ -10171,7 +10479,7 @@ async function putTasks(tasks) {
|
|
|
10171
10479
|
if (failure) throw failure.reason;
|
|
10172
10480
|
}
|
|
10173
10481
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10174
|
-
const executionPlanPath =
|
|
10482
|
+
const executionPlanPath = join25(executionPlanDir, "plan.json");
|
|
10175
10483
|
if (!existsSync17(executionPlanPath)) {
|
|
10176
10484
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10177
10485
|
}
|
|
@@ -10199,7 +10507,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10199
10507
|
}
|
|
10200
10508
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
10201
10509
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
10202
|
-
const encoder = readJsonFile(
|
|
10510
|
+
const encoder = readJsonFile(join25(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
10203
10511
|
const files = listFiles(executionPlanDir);
|
|
10204
10512
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
10205
10513
|
for (const file of files) {
|
|
@@ -10259,21 +10567,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
10259
10567
|
if (publication.storedBlobs.length > 0) {
|
|
10260
10568
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
10261
10569
|
}
|
|
10262
|
-
|
|
10263
|
-
const tempDir = mkdtempSync4(
|
|
10570
|
+
mkdirSync12(dirname12(planV2Dir), { recursive: true });
|
|
10571
|
+
const tempDir = mkdtempSync4(join25(dirname12(planV2Dir), ".plan-v2-build-"));
|
|
10264
10572
|
try {
|
|
10265
10573
|
for (const blob of publication.blobs) {
|
|
10266
10574
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
10267
10575
|
}
|
|
10268
|
-
|
|
10269
|
-
|
|
10576
|
+
writeFileSync9(
|
|
10577
|
+
join25(tempDir, "plan.json"),
|
|
10270
10578
|
canonicalJsonStringify(publication.manifest),
|
|
10271
10579
|
"utf-8"
|
|
10272
10580
|
);
|
|
10273
|
-
|
|
10581
|
+
renameWithRetry9(tempDir, planV2Dir);
|
|
10274
10582
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
10275
10583
|
} catch (error) {
|
|
10276
|
-
|
|
10584
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
10277
10585
|
throw error;
|
|
10278
10586
|
}
|
|
10279
10587
|
}
|
|
@@ -10313,7 +10621,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
10313
10621
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
10314
10622
|
}
|
|
10315
10623
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
10316
|
-
const stagingRoot = mkdtempSync4(
|
|
10624
|
+
const stagingRoot = mkdtempSync4(join25(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
10317
10625
|
const digests = /* @__PURE__ */ new Map();
|
|
10318
10626
|
try {
|
|
10319
10627
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -10324,7 +10632,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10324
10632
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
10325
10633
|
whileAudioMixes: () => putBlobs(
|
|
10326
10634
|
publisher,
|
|
10327
|
-
listFiles(
|
|
10635
|
+
listFiles(join25(stagingRoot, "compiled")).map(
|
|
10328
10636
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
10329
10637
|
)
|
|
10330
10638
|
),
|
|
@@ -10340,7 +10648,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10340
10648
|
}
|
|
10341
10649
|
throw error;
|
|
10342
10650
|
} finally {
|
|
10343
|
-
|
|
10651
|
+
rmSync11(stagingRoot, { recursive: true, force: true });
|
|
10344
10652
|
}
|
|
10345
10653
|
}
|
|
10346
10654
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -10349,14 +10657,14 @@ async function planV2(projectDir, config, planV2Dir) {
|
|
|
10349
10657
|
}
|
|
10350
10658
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
10351
10659
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
10352
|
-
stagingParentDir:
|
|
10660
|
+
stagingParentDir: dirname12(planV2Dir)
|
|
10353
10661
|
});
|
|
10354
10662
|
return resultFromManifest(planV2Dir, manifest);
|
|
10355
10663
|
}
|
|
10356
10664
|
function resultFromManifest(planV2Dir, manifest) {
|
|
10357
10665
|
return {
|
|
10358
10666
|
planDir: planV2Dir,
|
|
10359
|
-
manifestPath:
|
|
10667
|
+
manifestPath: join25(planV2Dir, "plan.json"),
|
|
10360
10668
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
10361
10669
|
planHash: manifest.planHash,
|
|
10362
10670
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -10492,7 +10800,7 @@ function parsePlanV2Manifest(value) {
|
|
|
10492
10800
|
return parsed;
|
|
10493
10801
|
}
|
|
10494
10802
|
function readPlanV2Manifest(planV2Dir) {
|
|
10495
|
-
const manifestPath =
|
|
10803
|
+
const manifestPath = join25(planV2Dir, "plan.json");
|
|
10496
10804
|
if (!existsSync17(manifestPath)) {
|
|
10497
10805
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
10498
10806
|
}
|
|
@@ -10515,7 +10823,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
10515
10823
|
if (!existsSync17(sourcePath)) {
|
|
10516
10824
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
10517
10825
|
}
|
|
10518
|
-
const stats =
|
|
10826
|
+
const stats = statSync7(sourcePath);
|
|
10519
10827
|
if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
|
|
10520
10828
|
throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
|
|
10521
10829
|
}
|
|
@@ -10537,17 +10845,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10537
10845
|
artifact,
|
|
10538
10846
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
10539
10847
|
}));
|
|
10540
|
-
|
|
10541
|
-
const tempDir = mkdtempSync4(
|
|
10848
|
+
mkdirSync12(dirname12(destinationDir), { recursive: true });
|
|
10849
|
+
const tempDir = mkdtempSync4(join25(dirname12(destinationDir), ".plan-v2-materialize-"));
|
|
10542
10850
|
try {
|
|
10543
10851
|
for (const { artifact, sourcePath } of verified) {
|
|
10544
|
-
const destinationPath =
|
|
10545
|
-
|
|
10546
|
-
|
|
10852
|
+
const destinationPath = join25(tempDir, ...artifact.path.split("/"));
|
|
10853
|
+
mkdirSync12(dirname12(destinationPath), { recursive: true });
|
|
10854
|
+
copyFileSync8(sourcePath, destinationPath);
|
|
10547
10855
|
}
|
|
10548
10856
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
10549
|
-
|
|
10550
|
-
|
|
10857
|
+
writeFileSync9(
|
|
10858
|
+
join25(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
10551
10859
|
canonicalJsonStringify({
|
|
10552
10860
|
manifest,
|
|
10553
10861
|
target,
|
|
@@ -10555,9 +10863,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10555
10863
|
}),
|
|
10556
10864
|
"utf-8"
|
|
10557
10865
|
);
|
|
10558
|
-
|
|
10866
|
+
renameWithRetry9(tempDir, destinationDir);
|
|
10559
10867
|
} catch (error) {
|
|
10560
|
-
|
|
10868
|
+
rmSync11(tempDir, { recursive: true, force: true });
|
|
10561
10869
|
throw error;
|
|
10562
10870
|
}
|
|
10563
10871
|
return {
|
|
@@ -10570,7 +10878,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
10570
10878
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
10571
10879
|
// before the container change carries the legacy name, and materializing it
|
|
10572
10880
|
// under the new one would point at a file that was never written.
|
|
10573
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
10881
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join25(destinationDir, audioArtifact.path) : null : null
|
|
10574
10882
|
};
|
|
10575
10883
|
}
|
|
10576
10884
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -10589,7 +10897,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
10589
10897
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
10590
10898
|
}
|
|
10591
10899
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
10592
|
-
const markerPath =
|
|
10900
|
+
const markerPath = join25(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
10593
10901
|
if (!existsSync17(markerPath)) return null;
|
|
10594
10902
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
10595
10903
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
@@ -10607,8 +10915,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
10607
10915
|
assertRemoteSources(remoteSources, required);
|
|
10608
10916
|
for (const artifact of required) {
|
|
10609
10917
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
10610
|
-
const path =
|
|
10611
|
-
if (!existsSync17(path) ||
|
|
10918
|
+
const path = join25(planDir, ...artifact.path.split("/"));
|
|
10919
|
+
if (!existsSync17(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
10612
10920
|
throw new PlanV2IntegrityError(
|
|
10613
10921
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
10614
10922
|
);
|
|
@@ -10621,23 +10929,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
10621
10929
|
}
|
|
10622
10930
|
|
|
10623
10931
|
// src/services/distributed/planV2Execution.ts
|
|
10624
|
-
import { mkdtempSync as mkdtempSync6, rmSync as
|
|
10932
|
+
import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
|
|
10625
10933
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10626
|
-
import { join as
|
|
10934
|
+
import { join as join28 } from "node:path";
|
|
10627
10935
|
|
|
10628
10936
|
// src/services/distributed/assemble.ts
|
|
10629
10937
|
import {
|
|
10630
10938
|
cpSync as cpSync3,
|
|
10631
10939
|
existsSync as existsSync18,
|
|
10632
|
-
mkdirSync as
|
|
10940
|
+
mkdirSync as mkdirSync13,
|
|
10633
10941
|
mkdtempSync as mkdtempSync5,
|
|
10634
|
-
readFileSync as
|
|
10942
|
+
readFileSync as readFileSync11,
|
|
10635
10943
|
readdirSync as readdirSync8,
|
|
10636
|
-
rmSync as
|
|
10637
|
-
statSync as
|
|
10638
|
-
writeFileSync as
|
|
10944
|
+
rmSync as rmSync12,
|
|
10945
|
+
statSync as statSync8,
|
|
10946
|
+
writeFileSync as writeFileSync10
|
|
10639
10947
|
} from "node:fs";
|
|
10640
|
-
import { dirname as
|
|
10948
|
+
import { dirname as dirname13, join as join26 } from "node:path";
|
|
10641
10949
|
import {
|
|
10642
10950
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
10643
10951
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
@@ -10645,9 +10953,9 @@ import {
|
|
|
10645
10953
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
10646
10954
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
10647
10955
|
resolveConfig as resolveConfig4,
|
|
10648
|
-
runFfmpeg as
|
|
10956
|
+
runFfmpeg as runFfmpeg8,
|
|
10649
10957
|
appendSoftwareEncoderThreadsArg,
|
|
10650
|
-
renameWithRetry as
|
|
10958
|
+
renameWithRetry as renameWithRetry10
|
|
10651
10959
|
} from "@hyperframes/engine";
|
|
10652
10960
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
10653
10961
|
function cfrRateControlArgs(locked) {
|
|
@@ -10673,13 +10981,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10673
10981
|
outputPath,
|
|
10674
10982
|
durationMs: Date.now() - start,
|
|
10675
10983
|
framesEncoded: plan2.totalFrames,
|
|
10676
|
-
fileSize:
|
|
10984
|
+
fileSize: statSync8(outputPath).size
|
|
10677
10985
|
};
|
|
10678
10986
|
}
|
|
10679
10987
|
const format = plan2.dimensions.format;
|
|
10680
10988
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
10681
|
-
if (!existsSync18(
|
|
10682
|
-
|
|
10989
|
+
if (!existsSync18(dirname13(outputPath))) {
|
|
10990
|
+
mkdirSync13(dirname13(outputPath), { recursive: true });
|
|
10683
10991
|
}
|
|
10684
10992
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
10685
10993
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -10701,10 +11009,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10701
11009
|
};
|
|
10702
11010
|
try {
|
|
10703
11011
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
10704
|
-
const finalPath =
|
|
11012
|
+
const finalPath = join26(workDir, `output.${format}`);
|
|
10705
11013
|
const audio = audioPath !== null && existsSync18(audioPath) ? audioPath : null;
|
|
10706
11014
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
10707
|
-
|
|
11015
|
+
renameWithRetry10(finalPath, outputPath);
|
|
10708
11016
|
log.info("[assemble] stage timings", {
|
|
10709
11017
|
...stageMs,
|
|
10710
11018
|
chunks: chunkPaths.length,
|
|
@@ -10712,7 +11020,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10712
11020
|
});
|
|
10713
11021
|
} finally {
|
|
10714
11022
|
try {
|
|
10715
|
-
|
|
11023
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
10716
11024
|
} catch (err) {
|
|
10717
11025
|
log.warn("[assemble] failed to remove work dir", {
|
|
10718
11026
|
workDir,
|
|
@@ -10720,7 +11028,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10720
11028
|
});
|
|
10721
11029
|
}
|
|
10722
11030
|
}
|
|
10723
|
-
const fileSize = existsSync18(outputPath) ?
|
|
11031
|
+
const fileSize = existsSync18(outputPath) ? statSync8(outputPath).size : 0;
|
|
10724
11032
|
return {
|
|
10725
11033
|
outputPath,
|
|
10726
11034
|
durationMs: Date.now() - start,
|
|
@@ -10729,18 +11037,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
10729
11037
|
};
|
|
10730
11038
|
}
|
|
10731
11039
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
10732
|
-
const planJsonPath =
|
|
10733
|
-
const chunksJsonPath =
|
|
11040
|
+
const planJsonPath = join26(planDir, "plan.json");
|
|
11041
|
+
const chunksJsonPath = join26(planDir, "meta", "chunks.json");
|
|
10734
11042
|
if (!existsSync18(planJsonPath)) {
|
|
10735
11043
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
10736
11044
|
}
|
|
10737
|
-
const plan2 = JSON.parse(
|
|
11045
|
+
const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
10738
11046
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
10739
11047
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
10740
11048
|
if (!existsSync18(chunksJsonPath)) {
|
|
10741
11049
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
10742
11050
|
}
|
|
10743
|
-
const chunks = JSON.parse(
|
|
11051
|
+
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
10744
11052
|
if (chunkPaths.length !== chunks.length) {
|
|
10745
11053
|
throw new Error(
|
|
10746
11054
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -10778,7 +11086,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
10778
11086
|
return args;
|
|
10779
11087
|
}
|
|
10780
11088
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
10781
|
-
const cfrOutputPath =
|
|
11089
|
+
const cfrOutputPath = join26(ctx.workDir, `cfr.${ctx.format}`);
|
|
10782
11090
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
10783
11091
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
10784
11092
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -10805,7 +11113,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
10805
11113
|
);
|
|
10806
11114
|
return;
|
|
10807
11115
|
}
|
|
10808
|
-
const paddedAudioPath =
|
|
11116
|
+
const paddedAudioPath = join26(ctx.workDir, "audio-padded.m4a");
|
|
10809
11117
|
const padTrimResult = await ctx.timed(
|
|
10810
11118
|
"audioMs",
|
|
10811
11119
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -10845,9 +11153,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
10845
11153
|
}
|
|
10846
11154
|
}
|
|
10847
11155
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
10848
|
-
const listPath =
|
|
11156
|
+
const listPath = join26(workDir, "concat-list.txt");
|
|
10849
11157
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
10850
|
-
|
|
11158
|
+
writeFileSync10(listPath, `${body}
|
|
10851
11159
|
`, "utf-8");
|
|
10852
11160
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
10853
11161
|
}
|
|
@@ -10862,7 +11170,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
|
|
|
10862
11170
|
};
|
|
10863
11171
|
}
|
|
10864
11172
|
async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
10865
|
-
const result = await
|
|
11173
|
+
const result = await runFfmpeg8(args, { signal, timeout });
|
|
10866
11174
|
if (!result.success) {
|
|
10867
11175
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
10868
11176
|
error: describeFfmpegFailure4(result, timeout),
|
|
@@ -10883,13 +11191,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
10883
11191
|
return lockedAudio(planDir).separateAudio === true;
|
|
10884
11192
|
}
|
|
10885
11193
|
function planFormat(planDir) {
|
|
10886
|
-
const plan2 = JSON.parse(
|
|
11194
|
+
const plan2 = JSON.parse(readFileSync11(join26(planDir, "plan.json"), "utf-8"));
|
|
10887
11195
|
return plan2.dimensions.format;
|
|
10888
11196
|
}
|
|
10889
11197
|
function lockedAudio(planDir) {
|
|
10890
|
-
const encoderJsonPath =
|
|
11198
|
+
const encoderJsonPath = join26(planDir, "meta", "encoder.json");
|
|
10891
11199
|
if (!existsSync18(encoderJsonPath)) return {};
|
|
10892
|
-
return JSON.parse(
|
|
11200
|
+
return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
10893
11201
|
}
|
|
10894
11202
|
function readCfrEncoder(planDir, format) {
|
|
10895
11203
|
if (format !== "mp4") {
|
|
@@ -10897,11 +11205,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
10897
11205
|
`[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.`
|
|
10898
11206
|
);
|
|
10899
11207
|
}
|
|
10900
|
-
const encoderJsonPath =
|
|
11208
|
+
const encoderJsonPath = join26(planDir, "meta", "encoder.json");
|
|
10901
11209
|
if (!existsSync18(encoderJsonPath)) {
|
|
10902
11210
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
10903
11211
|
}
|
|
10904
|
-
const encoderJson = JSON.parse(
|
|
11212
|
+
const encoderJson = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
10905
11213
|
if (encoderJson.encoder === "libx265-software") {
|
|
10906
11214
|
throw new Error(
|
|
10907
11215
|
`[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".`
|
|
@@ -10910,11 +11218,11 @@ function readCfrEncoder(planDir, format) {
|
|
|
10910
11218
|
return encoderJson;
|
|
10911
11219
|
}
|
|
10912
11220
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
10913
|
-
if (existsSync18(outputPath))
|
|
10914
|
-
|
|
11221
|
+
if (existsSync18(outputPath)) rmSync12(outputPath, { recursive: true, force: true });
|
|
11222
|
+
mkdirSync13(outputPath, { recursive: true });
|
|
10915
11223
|
let globalIdx = 0;
|
|
10916
11224
|
for (const chunkDir of chunkPaths) {
|
|
10917
|
-
if (!
|
|
11225
|
+
if (!statSync8(chunkDir).isDirectory()) {
|
|
10918
11226
|
throw new Error(
|
|
10919
11227
|
`[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
|
|
10920
11228
|
);
|
|
@@ -10924,8 +11232,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10924
11232
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
10925
11233
|
}
|
|
10926
11234
|
for (const frame of frames) {
|
|
10927
|
-
const dst =
|
|
10928
|
-
cpSync3(
|
|
11235
|
+
const dst = join26(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
11236
|
+
cpSync3(join26(chunkDir, frame), dst);
|
|
10929
11237
|
globalIdx += 1;
|
|
10930
11238
|
}
|
|
10931
11239
|
}
|
|
@@ -10935,13 +11243,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10935
11243
|
);
|
|
10936
11244
|
}
|
|
10937
11245
|
if (audioPath !== null && existsSync18(audioPath)) {
|
|
10938
|
-
const sidecar =
|
|
11246
|
+
const sidecar = join26(outputPath, MIXED_AUDIO_FILENAME4);
|
|
10939
11247
|
cpSync3(audioPath, sidecar);
|
|
10940
11248
|
}
|
|
10941
11249
|
let fileSize = 0;
|
|
10942
11250
|
for (const name of readdirSync8(outputPath)) {
|
|
10943
11251
|
try {
|
|
10944
|
-
fileSize +=
|
|
11252
|
+
fileSize += statSync8(join26(outputPath, name)).size;
|
|
10945
11253
|
} catch {
|
|
10946
11254
|
}
|
|
10947
11255
|
}
|
|
@@ -10955,9 +11263,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
10955
11263
|
|
|
10956
11264
|
// src/services/distributed/renderChunk.ts
|
|
10957
11265
|
import { randomBytes } from "node:crypto";
|
|
10958
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
11266
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync12, readdirSync as readdirSync9, rmSync as rmSync13, writeFileSync as writeFileSync11 } from "node:fs";
|
|
10959
11267
|
import { cpus } from "node:os";
|
|
10960
|
-
import { join as
|
|
11268
|
+
import { join as join27 } from "node:path";
|
|
10961
11269
|
import {
|
|
10962
11270
|
assertSwiftShader,
|
|
10963
11271
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -11076,10 +11384,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
11076
11384
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
11077
11385
|
}
|
|
11078
11386
|
function hashChunkOutput(outputPath, kind) {
|
|
11079
|
-
if (kind === "file") return sha256Hex(
|
|
11387
|
+
if (kind === "file") return sha256Hex(readFileSync12(outputPath));
|
|
11080
11388
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11081
11389
|
const lines = entries.map(
|
|
11082
|
-
(name) => `${name}\0${sha256Hex(
|
|
11390
|
+
(name) => `${name}\0${sha256Hex(readFileSync12(join27(outputPath, name)))}`
|
|
11083
11391
|
);
|
|
11084
11392
|
return sha256Hex(lines.join("\0"));
|
|
11085
11393
|
}
|
|
@@ -11096,16 +11404,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
11096
11404
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
11097
11405
|
const start = Date.now();
|
|
11098
11406
|
const log = defaultLogger;
|
|
11099
|
-
const planJsonPath =
|
|
11100
|
-
const encoderJsonPath =
|
|
11101
|
-
const chunksJsonPath =
|
|
11407
|
+
const planJsonPath = join27(planDir, "plan.json");
|
|
11408
|
+
const encoderJsonPath = join27(planDir, "meta", "encoder.json");
|
|
11409
|
+
const chunksJsonPath = join27(planDir, "meta", "chunks.json");
|
|
11102
11410
|
if (!existsSync19(planJsonPath)) {
|
|
11103
11411
|
throw new RenderChunkValidationError(
|
|
11104
11412
|
MISSING_PLAN_ARTIFACT,
|
|
11105
11413
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
11106
11414
|
);
|
|
11107
11415
|
}
|
|
11108
|
-
const plan2 = JSON.parse(
|
|
11416
|
+
const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
|
|
11109
11417
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
11110
11418
|
const planHashStarted = Date.now();
|
|
11111
11419
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -11122,13 +11430,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11122
11430
|
);
|
|
11123
11431
|
}
|
|
11124
11432
|
}
|
|
11125
|
-
const encoder = JSON.parse(
|
|
11126
|
-
const chunks = JSON.parse(
|
|
11127
|
-
const videosJsonPath =
|
|
11433
|
+
const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
|
|
11434
|
+
const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
|
|
11435
|
+
const videosJsonPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11128
11436
|
let planVideos = null;
|
|
11129
11437
|
if (existsSync19(videosJsonPath)) {
|
|
11130
11438
|
try {
|
|
11131
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
11439
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
|
|
11132
11440
|
} catch (err) {
|
|
11133
11441
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
11134
11442
|
throw new RenderChunkValidationError(
|
|
@@ -11156,7 +11464,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11156
11464
|
}
|
|
11157
11465
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11158
11466
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11159
|
-
const compiledDir =
|
|
11467
|
+
const compiledDir = join27(planDir, "compiled");
|
|
11160
11468
|
if (!existsSync19(compiledDir)) {
|
|
11161
11469
|
throw new RenderChunkValidationError(
|
|
11162
11470
|
MISSING_PLAN_ARTIFACT,
|
|
@@ -11213,9 +11521,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11213
11521
|
forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
|
|
11214
11522
|
};
|
|
11215
11523
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
11216
|
-
|
|
11217
|
-
const framesDir =
|
|
11218
|
-
|
|
11524
|
+
mkdirSync14(workDir, { recursive: true });
|
|
11525
|
+
const framesDir = join27(workDir, "captured-frames");
|
|
11526
|
+
mkdirSync14(framesDir, { recursive: true });
|
|
11219
11527
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
11220
11528
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
11221
11529
|
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -11239,7 +11547,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11239
11547
|
dimensions: plan2.dimensions,
|
|
11240
11548
|
encoder,
|
|
11241
11549
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11242
|
-
html:
|
|
11550
|
+
html: readFileSync12(join27(compiledDir, "index.html"), "utf-8"),
|
|
11243
11551
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
11244
11552
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
11245
11553
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -11368,10 +11676,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11368
11676
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
11369
11677
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
11370
11678
|
if (isPngSequence) {
|
|
11371
|
-
if (!existsSync19(outputChunkPath))
|
|
11679
|
+
if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
11372
11680
|
} else {
|
|
11373
|
-
const outDir =
|
|
11374
|
-
if (!existsSync19(outDir))
|
|
11681
|
+
const outDir = join27(outputChunkPath, "..");
|
|
11682
|
+
if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
11375
11683
|
}
|
|
11376
11684
|
const encodeInput = {
|
|
11377
11685
|
job,
|
|
@@ -11482,8 +11790,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11482
11790
|
return observedChunkCaptureMode(capturePerfs);
|
|
11483
11791
|
},
|
|
11484
11792
|
resetForScreenshotRetry: () => {
|
|
11485
|
-
|
|
11486
|
-
|
|
11793
|
+
rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11794
|
+
mkdirSync14(framesDir, { recursive: true });
|
|
11487
11795
|
capturePerfs.length = 0;
|
|
11488
11796
|
captureAttempts.length = 0;
|
|
11489
11797
|
job.framesRendered = 0;
|
|
@@ -11525,8 +11833,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11525
11833
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
11526
11834
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
11527
11835
|
});
|
|
11528
|
-
|
|
11529
|
-
|
|
11836
|
+
rmSync13(videoOnlyPath, { force: true });
|
|
11837
|
+
rmSync13(audioScratchPath, { force: true });
|
|
11530
11838
|
}
|
|
11531
11839
|
encodeStageMs = Date.now() - encodeStarted;
|
|
11532
11840
|
} finally {
|
|
@@ -11573,11 +11881,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11573
11881
|
producerVersion: plan2.producerVersion,
|
|
11574
11882
|
ffmpegVersion
|
|
11575
11883
|
};
|
|
11576
|
-
|
|
11884
|
+
writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
11577
11885
|
`, "utf-8");
|
|
11578
11886
|
try {
|
|
11579
|
-
|
|
11580
|
-
|
|
11887
|
+
rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11888
|
+
rmSync13(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
11581
11889
|
} catch (err) {
|
|
11582
11890
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
11583
11891
|
workDir,
|
|
@@ -11606,18 +11914,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11606
11914
|
|
|
11607
11915
|
// src/services/distributed/planV2Execution.ts
|
|
11608
11916
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
11609
|
-
const workRoot = mkdtempSync6(
|
|
11610
|
-
const materializedPlanDir =
|
|
11917
|
+
const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-chunk-"));
|
|
11918
|
+
const materializedPlanDir = join28(workRoot, "plan");
|
|
11611
11919
|
try {
|
|
11612
11920
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
11613
11921
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
11614
11922
|
} finally {
|
|
11615
|
-
|
|
11923
|
+
rmSync14(workRoot, { recursive: true, force: true });
|
|
11616
11924
|
}
|
|
11617
11925
|
}
|
|
11618
11926
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
11619
|
-
const workRoot = mkdtempSync6(
|
|
11620
|
-
const materializedPlanDir =
|
|
11927
|
+
const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-assembler-"));
|
|
11928
|
+
const materializedPlanDir = join28(workRoot, "plan");
|
|
11621
11929
|
try {
|
|
11622
11930
|
const materialized = materializePlanV2Target(
|
|
11623
11931
|
planV2Dir,
|
|
@@ -11632,7 +11940,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
11632
11940
|
options
|
|
11633
11941
|
);
|
|
11634
11942
|
} finally {
|
|
11635
|
-
|
|
11943
|
+
rmSync14(workRoot, { recursive: true, force: true });
|
|
11636
11944
|
}
|
|
11637
11945
|
}
|
|
11638
11946
|
|
|
@@ -11881,9 +12189,9 @@ function validateIntDimension(field, value) {
|
|
|
11881
12189
|
}
|
|
11882
12190
|
|
|
11883
12191
|
// src/services/distributed/projectHash.ts
|
|
11884
|
-
import { closeSync as
|
|
11885
|
-
import { createHash as
|
|
11886
|
-
import { join as
|
|
12192
|
+
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
|
|
12193
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12194
|
+
import { join as join29 } from "node:path";
|
|
11887
12195
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
11888
12196
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
11889
12197
|
function listSiteFiles(projectDir) {
|
|
@@ -11892,11 +12200,11 @@ function listSiteFiles(projectDir) {
|
|
|
11892
12200
|
function walk(dir, rel) {
|
|
11893
12201
|
for (const name of readdirSync10(dir).sort()) {
|
|
11894
12202
|
if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
|
|
11895
|
-
const full =
|
|
12203
|
+
const full = join29(dir, name);
|
|
11896
12204
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
11897
12205
|
let stat;
|
|
11898
12206
|
try {
|
|
11899
|
-
stat =
|
|
12207
|
+
stat = statSync9(full);
|
|
11900
12208
|
} catch (err) {
|
|
11901
12209
|
throw new Error(`[listSiteFiles] cannot read ${relPath} (dangling symlink?)`, {
|
|
11902
12210
|
cause: err
|
|
@@ -11917,26 +12225,26 @@ function listSiteFiles(projectDir) {
|
|
|
11917
12225
|
return files;
|
|
11918
12226
|
}
|
|
11919
12227
|
function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
11920
|
-
const hash =
|
|
12228
|
+
const hash = createHash7("sha256").update(SITE_ID_SCHEME);
|
|
11921
12229
|
const chunk = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
11922
12230
|
for (const rel of files) {
|
|
11923
12231
|
hash.update(rel);
|
|
11924
12232
|
hash.update("\0");
|
|
11925
|
-
const fd =
|
|
12233
|
+
const fd = openSync5(join29(projectDir, rel), "r");
|
|
11926
12234
|
try {
|
|
11927
12235
|
let read;
|
|
11928
|
-
while ((read =
|
|
12236
|
+
while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
|
|
11929
12237
|
hash.update(chunk.subarray(0, read));
|
|
11930
12238
|
}
|
|
11931
12239
|
} finally {
|
|
11932
|
-
|
|
12240
|
+
closeSync5(fd);
|
|
11933
12241
|
}
|
|
11934
12242
|
}
|
|
11935
12243
|
return hash.digest("hex").slice(0, 16);
|
|
11936
12244
|
}
|
|
11937
12245
|
|
|
11938
12246
|
// src/services/distributed/audioSidecar.ts
|
|
11939
|
-
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as
|
|
12247
|
+
import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
|
|
11940
12248
|
import { extname as extname5 } from "node:path";
|
|
11941
12249
|
var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
|
|
11942
12250
|
var AUDIO_SIDECAR_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
|
|
@@ -11947,7 +12255,7 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11947
12255
|
const audio = await extractAudioMetadata4(inputPath, { signal }).catch(() => null);
|
|
11948
12256
|
if (!audio) return await ffmpegIsMissing(signal) ? "unavailable" : "none";
|
|
11949
12257
|
if (audio.audioStreamCount !== 1 || (audio.audioStreamLeadSeconds ?? 0) > 0) return "none";
|
|
11950
|
-
const result = await
|
|
12258
|
+
const result = await runFfmpeg9(
|
|
11951
12259
|
["-v", "error", "-y", "-i", inputPath, "-map", "0:a:0", "-vn", "-c:a", "copy"].concat([
|
|
11952
12260
|
"-movflags",
|
|
11953
12261
|
"+faststart",
|
|
@@ -11961,10 +12269,10 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
|
|
|
11961
12269
|
return isMissing(result) ? "unavailable" : "none";
|
|
11962
12270
|
}
|
|
11963
12271
|
var isMissing = (result) => result.error?.code === "ENOENT";
|
|
11964
|
-
var ffmpegIsMissing = async (signal) => isMissing(await
|
|
12272
|
+
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
|
|
11965
12273
|
|
|
11966
12274
|
// src/services/distributed/probeFacts.ts
|
|
11967
|
-
import { writeFileSync as
|
|
12275
|
+
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
11968
12276
|
import { extname as extname6 } from "node:path";
|
|
11969
12277
|
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
11970
12278
|
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -11973,7 +12281,7 @@ function wantsProbeFacts(path) {
|
|
|
11973
12281
|
}
|
|
11974
12282
|
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
11975
12283
|
try {
|
|
11976
|
-
|
|
12284
|
+
writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
11977
12285
|
return "built";
|
|
11978
12286
|
} catch (error) {
|
|
11979
12287
|
if (signal?.aborted) throw error;
|