@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.
@@ -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 copyFileSync5,
4
+ copyFileSync as copyFileSync6,
5
5
  cpSync as cpSync2,
6
6
  existsSync as existsSync15,
7
7
  linkSync,
8
- mkdirSync as mkdirSync9,
9
- rmSync as rmSync8,
10
- writeFileSync as writeFileSync6
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 join21, relative as relative5, sep as sep2 } from "node:path";
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 renameWithRetry6,
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 mkdirSync5,
70
- readFileSync as readFileSync5,
69
+ mkdirSync as mkdirSync6,
70
+ readFileSync as readFileSync6,
71
71
  readdirSync as readdirSync3,
72
- rmSync as rmSync6,
73
- statSync as statSync4,
74
- writeFileSync as writeFileSync3,
75
- copyFileSync as copyFileSync4,
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 join13, dirname as dirname7, resolve as resolve6 } from "path";
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(child2, parent, options = {}) {
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(child2);
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
- ...range ? { start: range.start, end: range.end } : {}
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((resolve9) => {
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
- resolve9({
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 join6 } from "node:path";
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 createHash3 } from "node:crypto";
1791
- import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
1792
- import { join as join5, dirname as dirname4, resolve as resolve5, basename as basename3, relative as relative2 } from "path";
1793
- import { parseHTML as parseHTML4 } from "linkedom";
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 resolveGifSourcePath(src, options) {
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 = resolveGifSourcePath(src, options);
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/htmlCompiler.ts
2341
- import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
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
- fingerprintElementId,
2348
- isNotMediaPayload,
2349
- NotMediaPayloadError,
2350
- probeMediaProfile,
2351
- resolveProjectRelativeSrc as resolveProjectRelativeSrc2
2352
- } from "@hyperframes/engine";
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((resolve9) => {
2436
+ return new Promise((resolve10) => {
2368
2437
  this.queue.push(() => {
2369
2438
  this.active++;
2370
- resolve9(() => this.release());
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 parseHTML4(html).document;
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 } = parseHTML4(html);
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 } = parseHTML4(html);
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 = resolve5(projectDir, srcPath);
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 } = parseHTML4(html);
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 } = parseHTML4(html);
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)) mkdirSync3(downloadDir, { recursive: true });
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 = join5(baseDir, 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 } = parseHTML4(html);
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 } = parseHTML4(html);
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 = resolve5(projectDir, srcPath);
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
- dirname4(item.filePath),
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 } = parseHTML4(html);
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 } = parseHTML4(html);
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 } = parseHTML4(html);
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(resolve5(projectDir, srcPath));
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) => parseHTML4(htmlStr).document,
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(resolve5(projectDir, path)),
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 } = parseHTML4(html);
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 = createHash3(strongest).update(bytes).digest();
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 } = parseHTML4(fullHtml);
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 = resolve5(projectDir);
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 = resolve5(absProjectDir, trimmed);
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 } = parseHTML4(html);
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)) mkdirSync3(remoteDir, { recursive: true });
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
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
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
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
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
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
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
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
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 : resolve5(projectDir, 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(resolve5(projectDir, path));
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 } = parseHTML4(html);
3621
- const assetExists = (path) => existsSync6(resolve5(projectDir, path));
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
- readFileSync3(htmlPath, "utf-8"),
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(htmlWithLocalizedFonts, {
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 = resolve5(projectDir, video.src);
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 } = parseHTML4(html);
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, join6(workDir, "downloads"), {
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 ? join6(cfg.extractCacheDir, "animated-gif") : void 0,
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: join6(workDir, "compiled"),
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 join7 } from "node:path";
4286
- import { parseHTML as parseHTML5 } from "linkedom";
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 } = parseHTML5(html);
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 } = parseHTML5(html);
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 } = parseHTML5(html);
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 } = parseHTML5(html);
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 } = parseHTML5(html);
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: join7(workDir, "compiled"),
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
- join7(workDir, "probe"),
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
- join7(workDir, "probe-screenshot"),
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
- join7(workDir, "downloads")
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: join7(workDir, "compiled"),
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 join8 } from "node:path";
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: join8(compiledDir, "__hyperframes_video_frames"),
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: [join8(compiledDir, REMOTE_MEDIA_SUBDIR)],
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 join10 } from "node:path";
5405
- import { rmSync as rmSync4 } from "node:fs";
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 rmSync3 } from "node:fs";
5415
- import { dirname as dirname5, join as join9 } from "node:path";
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 renameWithRetry4,
5424
- runFfmpeg as runFfmpeg4,
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 runFfmpeg3
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 runFfmpeg3(
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) rmSync3(path, { force: true });
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(join9(dirname5(input.audioPath), ".true-peak-"));
5666
- const correctedPath = join9(scratchDir, "audio.m4a");
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
- renameWithRetry4(correctedPath, input.audioPath);
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) rmSync3(scratchDir, { recursive: true, force: true });
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 runFfmpeg4(args, { signal, timeout: timeoutMs }), timeoutMs);
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 = join10(input.workDir, "audio-source.wav");
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
- rmSync4(pcmSourcePath, { force: true });
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 = join10(workDir, MIXED_AUDIO_FILENAME);
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
- join10(workDir, "audio-work"),
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 join11 } from "node:path";
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(join11(workDir, attempt));
6349
+ total += sampleDirectoryBytes(join12(workDir, attempt));
6042
6350
  continue;
6043
6351
  }
6044
6352
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
6045
- const attemptDir = join11(workDir, attempt);
6353
+ const attemptDir = join12(workDir, attempt);
6046
6354
  for (const name of readdirSync(attemptDir)) {
6047
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join11(attemptDir, name));
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 copyFileSync3,
6681
+ copyFileSync as copyFileSync4,
6374
6682
  existsSync as existsSync9,
6375
- mkdirSync as mkdirSync4,
6683
+ mkdirSync as mkdirSync5,
6376
6684
  readdirSync as readdirSync2,
6377
- readFileSync as readFileSync4,
6378
- rmSync as rmSync5,
6379
- statSync as statSync3,
6380
- writeFileSync as writeFileSync2
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 dirname6, join as join12 } from "node:path";
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 runFfmpeg5,
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 runFfmpeg5(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
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 = readFileSync4(outputPath);
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
- writeFileSync2(outputPath, gif);
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 runFfmpeg5(args, run);
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(readFileSync4(outputPath)) !== false) {
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) ? statSync3(outputPath).size : 0;
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
- rmSync5(input.palettePath, { force: true });
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((resolve9) => wake = resolve9);
6897
+ await new Promise((resolve10) => wake = resolve10);
6590
6898
  }
6591
6899
  if (signal.aborted) return;
6592
- const mergedPath = join12(input.framesDir, formatCaptureFrameName(next, "jpg"));
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)) mkdirSync4(outputPath, { recursive: true });
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 = join12(outputPath, formatExportFrameName(i, "png"));
6676
- copyFileSync3(join12(framesDir, name), dst);
6983
+ const dst = join13(outputPath, formatExportFrameName(i, "png"));
6984
+ copyFileSync4(join13(framesDir, name), dst);
6677
6985
  });
6678
6986
  if (hasAudio && audioOutputPath && existsSync9(audioOutputPath)) {
6679
- copyFileSync3(audioOutputPath, join12(outputPath, MIXED_AUDIO_FILENAME2));
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: join12(dirname6(videoOnlyPath), "gif-palette.png"),
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 = join13(current, name);
7066
+ const full = join14(current, name);
6759
7067
  try {
6760
- const st = statSync4(full);
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 = join13(framesDir, formatCaptureFrameName(frameIndex, frameExt));
6845
- const missing = !existsSync10(framePath) || statSync4(framePath).size <= 8;
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) => join13(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${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 = join13(framesDir, formatCaptureFrameName(frameIndex, frameExt));
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 = join13(options.workDir, `capture-attempt-${attempt}`);
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 join16 } from "node:path";
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 join15 } from "node:path";
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 dirname8, join as join14 } from "node:path";
7167
- import { existsSync as existsSync11, readFileSync as readFileSync6 } from "node:fs";
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 = join14(planDir, name);
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 = dirname8(fileURLToPath3(import.meta.url));
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 = join14(current, "package.json");
7750
+ const candidate = join15(current, "package.json");
7443
7751
  if (existsSync11(candidate)) {
7444
7752
  try {
7445
- const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
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 = dirname8(current);
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 = join15(planDir, "video-frames", v.videoId);
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, join15(outputDir, frameName));
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 = join15(input.outputRoot, video.videoId);
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 ?? join15(input.planDir, ...range.sourcePath.split("/"));
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: join16(workDir, "frame-cost-video-frames"),
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 = join16(planDir, "compiled");
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
- join16(workDir, "frame-cost"),
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 mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
7776
- import { join as join17, relative as relative3, resolve as resolve7 } from "node:path";
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 createHash4 } from "node:crypto";
7875
- import { closeSync as closeSync2, createReadStream as createReadStream3, openSync as openSync2, readSync } from "node:fs";
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 = createHash4("sha256");
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 = createHash4("sha256");
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 = createHash4("sha256");
8239
+ const hash = createHash5("sha256");
7932
8240
  const buffer = Buffer.allocUnsafe(1024 * 1024);
7933
- const fd = openSync2(path, "r");
8241
+ const fd = openSync3(path, "r");
7934
8242
  try {
7935
8243
  let bytesRead = 0;
7936
8244
  do {
7937
- bytesRead = readSync(fd, buffer, 0, buffer.byteLength, null);
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
- closeSync2(fd);
8249
+ closeSync3(fd);
7942
8250
  }
7943
8251
  return hash.digest("hex");
7944
8252
  }
7945
8253
  async function sha256FileAsync(path) {
7946
- const hash = createHash4("sha256");
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 = resolve7(planDir);
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 = join17(dir, entry.name);
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 = readFileSync7(file.absolutePath);
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
- writeFileSync4(join17(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8343
+ writeFileSync5(join18(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8036
8344
  }
8037
8345
  function readPlanRemoteFiles(planDir) {
8038
- const path = join17(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8039
- return existsSync13(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
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 = join17(planDir, "plan.json");
8043
- const encoderJsonPath = join17(planDir, "meta", "encoder.json");
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(readFileSync7(planJsonPath, "utf-8"));
8051
- const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
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 = join17(planDir, "meta");
8081
- if (!existsSync13(metaDir)) mkdirSync6(metaDir, { recursive: true });
8082
- writeFileSync4(
8083
- join17(metaDir, "composition.json"),
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
- writeFileSync4(join17(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8091
- writeFileSync4(join17(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
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 = join17(planDir, "plan.json");
8116
- writeFileSync4(planJsonPath, `${JSON.stringify(planJson, null, 2)}
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 closeSync3, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync8 } from "node:fs";
8158
- import { writeFileSync as writeFileSync5, writeSync } from "node:fs";
8159
- import { join as join19 } from "node:path";
8160
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
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 mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync7 } from "node:fs";
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 renameWithRetry5 } from "@hyperframes/engine";
8166
- import { dirname as dirname9, join as join18 } from "node:path";
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 boxes = [];
8180
- for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
8181
- boxes.push(boxAt(buf, at, at, parent.end));
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 boxes;
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 child(buf, parent, type) {
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 = child(moov, edts, "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 readSync2(moov, stbl, sampleCount) {
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, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
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, child(moov, stbl, "stsc"), 3);
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 = child(moov, trak, "mdia");
8277
- const hdlr = child(moov, mdia, "hdlr");
8278
- const stbl = child(moov, child(moov, mdia, "minf"), "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, child(moov, stbl, "stts"), 2);
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, child(moov, stbl, "stsz"));
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 = child(moov, stbl, "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, child(moov, mdia, "mdhd")),
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: readSync2(moov, stbl, sizes.length),
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 = child(template, root, "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, child(template, track.trak, "tkhd")));
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
- mkdirSync7(dirname9(outputPath), { recursive: true });
8838
+ mkdirSync8(dirname10(outputPath), { recursive: true });
8531
8839
  const workDir = mkdtempSync2(`${outputPath}.stitch-`);
8532
- const out = createWriteStream(join18(workDir, "out.mp4"));
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
- renameWithRetry5(join18(workDir, "out.mp4"), outputPath);
8846
+ renameWithRetry6(join19(workDir, "out.mp4"), outputPath);
8539
8847
  } finally {
8540
8848
  out.destroy();
8541
- rmSync7(workDir, { recursive: true, force: true });
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 = readFileSync8(input.audioPath);
8938
+ const bytes = readFileSync9(input.audioPath);
8631
8939
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
8632
- mkdirSync8(join19(input.planDir, SLICE_DIR), { recursive: true });
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
- writeFileSync5(join19(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
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
- writeFileSync5(join19(input.planDir, SLICE_FRAME), Buffer.concat(frame));
8644
- writeFileSync5(join19(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
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 = join19(planDir, SLICE_LAYOUT);
8955
+ const layoutPath = join20(planDir, SLICE_LAYOUT);
8648
8956
  if (!existsSync14(layoutPath)) return resolvePlanAudioPath(planDir);
8649
- const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
8650
- const frame = readFileSync8(join19(planDir, SLICE_FRAME));
8651
- const fd = openSync3(scratchPath, "w");
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 = readFileSync8(join19(planDir, chunkAudioSlicePath(chunkIndex)));
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
- closeSync3(fd);
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 runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
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 createHash5 } from "node:crypto";
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 join20, relative as relative4 } from "node:path";
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 createHash5("sha256").update(value).digest("hex").slice(0, 12);
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 = join20(dir, entry.name);
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
- join21("meta", "composition.json"),
9048
- join21("meta", "encoder.json"),
9049
- join21("meta", "chunks.json")
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
- rmSync8(join21(planDir, relativePath), { force: true });
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 : join21(input.planDir, "video-frames", stagedRel);
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(join21(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
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 = join21(input.planDir, ...result.split("/"));
9210
- mkdirSync9(join21(input.planDir, "video-sources"), { recursive: true });
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
- copyFileSync5(current, destination);
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)) mkdirSync9(planDir, { recursive: true });
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 = join21(projectDir, entryFile);
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 = join21(planDir, ".plan-work");
9264
- rmSync8(workDir, { recursive: true, force: true });
9265
- mkdirSync9(workDir, { recursive: true });
9266
- const compiledDir = join21(workDir, "compiled");
9267
- mkdirSync9(compiledDir, { recursive: true });
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 = join21(planDir, "compiled");
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) => join21(projectDir, ...rel.split("/"));
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 = join21(compiledDir, "__hyperframes_video_frames");
9470
- const videoFramesDst = join21(planDir, "video-frames");
9471
- if (existsSync15(videoFramesDst)) rmSync8(videoFramesDst, { recursive: true, force: true });
9472
- const videoSourcesDir = join21(planDir, "video-sources");
9473
- rmSync8(videoSourcesDir, { recursive: true, force: true });
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
- renameWithRetry6(stagedVideoFrames, videoFramesDst);
9783
+ renameWithRetry7(stagedVideoFrames, videoFramesDst);
9476
9784
  } else {
9477
- mkdirSync9(videoFramesDst, { recursive: true });
9785
+ mkdirSync10(videoFramesDst, { recursive: true });
9478
9786
  }
9479
- if (existsSync15(finalCompiledDir)) rmSync8(finalCompiledDir, { recursive: true, force: true });
9480
- renameWithRetry6(compiledDir, finalCompiledDir);
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
- mkdirSync9(join21(planDir, "meta"), { recursive: true });
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
- writeFileSync6(
9519
- join21(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
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 = join21(planDir, PLAN_AUDIO_RELATIVE_PATH);
9906
+ const planAudioPath = join22(planDir, PLAN_AUDIO_RELATIVE_PATH);
9599
9907
  if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
9600
- renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
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
- rmSync8(workDir, { recursive: true, force: true });
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
- renameWithRetry6(mixed.audioOutputPath, outputPath);
10022
+ renameWithRetry7(mixed.audioOutputPath, outputPath);
9715
10023
  return { hasAudio: true };
9716
10024
  } finally {
9717
- rmSync8(c.workDir, { recursive: true, force: true });
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 copyFileSync7,
10047
+ copyFileSync as copyFileSync8,
9740
10048
  existsSync as existsSync17,
9741
10049
  lstatSync as lstatSync2,
9742
- mkdirSync as mkdirSync11,
10050
+ mkdirSync as mkdirSync12,
9743
10051
  mkdtempSync as mkdtempSync4,
9744
- readFileSync as readFileSync9,
10052
+ readFileSync as readFileSync10,
9745
10053
  readdirSync as readdirSync7,
9746
- rmSync as rmSync10,
9747
- statSync as statSync6,
9748
- writeFileSync as writeFileSync8
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 dirname11, isAbsolute as isAbsolute4, join as join24, relative as relative6, resolve as resolve8, sep as sep3 } from "node:path";
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 renameWithRetry8 } from "@hyperframes/engine";
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 copyFileSync6,
10065
+ copyFileSync as copyFileSync7,
9758
10066
  existsSync as existsSync16,
9759
10067
  linkSync as linkSync2,
9760
- mkdirSync as mkdirSync10,
10068
+ mkdirSync as mkdirSync11,
9761
10069
  mkdtempSync as mkdtempSync3,
9762
- rmSync as rmSync9,
9763
- statSync as statSync5,
9764
- writeFileSync as writeFileSync7
10070
+ rmSync as rmSync10,
10071
+ statSync as statSync6,
10072
+ writeFileSync as writeFileSync8
9765
10073
  } from "node:fs";
9766
- import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
9767
- import { dirname as dirname10, join as join23 } from "node:path";
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 join22 } from "node:path";
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 join22(root, "artifacts", "sha256", digest.slice(0, 2), digest);
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
- mkdirSync10(dirname10(destinationDir), { recursive: true });
9833
- this.temporaryDir = mkdtempSync3(join23(dirname10(destinationDir), ".plan-v2-publish-"));
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 = statSync5(blob.sourcePath).size;
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
- mkdirSync10(dirname10(destinationPath), { recursive: true });
9846
- const stagingDir = mkdtempSync3(join23(dirname10(destinationPath), ".plan-v2-blob-"));
9847
- const temporaryPath = join23(stagingDir, "blob");
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
- copyFileSync6(blob.sourcePath, temporaryPath);
10161
+ copyFileSync7(blob.sourcePath, temporaryPath);
9854
10162
  }
9855
- renameWithRetry7(temporaryPath, destinationPath);
10163
+ renameWithRetry8(temporaryPath, destinationPath);
9856
10164
  } finally {
9857
- rmSync9(stagingDir, { recursive: true, force: true });
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
- writeFileSync7(join23(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
9869
- renameWithRetry7(this.temporaryDir, this.destinationDir);
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
- rmSync9(this.temporaryDir, { recursive: true, force: true });
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 = readFileSync9(path, "utf-8");
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 = resolve8(root);
10218
+ const rootResolved = resolve9(root);
9911
10219
  function walk(dir) {
9912
10220
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
9913
- const absolutePath = join24(dir, entry.name);
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 = join24(executionPlanDir, "video-frames");
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 = join24(videoRoot, videoEntry.name);
10252
+ const videoDir = join25(videoRoot, videoEntry.name);
9945
10253
  if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
9946
- const sentinelPath = join24(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
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 = resolve8(planDir, "video-frames");
9957
- const outputDir = resolve8(videoRoot, videoId);
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, join24(outputDir, String(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(join24(outputDir, frameName));
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, join24(outputDir, frameName));
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 = join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
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
- mkdirSync11(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
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 = join24(executionPlanDir, "video-frames");
10383
+ const videoRoot = join25(executionPlanDir, "video-frames");
10076
10384
  const hasExtractedFrames = existsSync17(videoRoot) && listFiles(videoRoot).length > 0;
10077
- const videosPath = join24(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
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 = join24(executionPlanDir, "meta", "chunks.json");
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(resolve8(input.executionPlanDir), payload.framePath).split(sep3).join("/");
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
- mkdirSync11(dirname11(destinationPath), { recursive: true });
10137
- const temporaryDir = mkdtempSync4(join24(dirname11(destinationPath), ".plan-v2-blob-"));
10138
- const temporaryPath = join24(temporaryDir, "blob");
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
- copyFileSync7(sourcePath, temporaryPath);
10141
- renameWithRetry8(temporaryPath, destinationPath);
10448
+ copyFileSync8(sourcePath, temporaryPath);
10449
+ renameWithRetry9(temporaryPath, destinationPath);
10142
10450
  } finally {
10143
- rmSync10(temporaryDir, { recursive: true, force: true });
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: statSync6(absolutePath).size
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 = join24(executionPlanDir, "plan.json");
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(join24(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
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
- mkdirSync11(dirname11(planV2Dir), { recursive: true });
10263
- const tempDir = mkdtempSync4(join24(dirname11(planV2Dir), ".plan-v2-build-"));
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
- writeFileSync8(
10269
- join24(tempDir, "plan.json"),
10576
+ writeFileSync9(
10577
+ join25(tempDir, "plan.json"),
10270
10578
  canonicalJsonStringify(publication.manifest),
10271
10579
  "utf-8"
10272
10580
  );
10273
- renameWithRetry8(tempDir, planV2Dir);
10581
+ renameWithRetry9(tempDir, planV2Dir);
10274
10582
  return resultFromManifest(planV2Dir, publication.manifest);
10275
10583
  } catch (error) {
10276
- rmSync10(tempDir, { recursive: true, force: true });
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(join24(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
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(join24(stagingRoot, "compiled")).map(
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
- rmSync10(stagingRoot, { recursive: true, force: true });
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: dirname11(planV2Dir)
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: join24(planV2Dir, "plan.json"),
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 = join24(planV2Dir, "plan.json");
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 = statSync6(sourcePath);
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
- mkdirSync11(dirname11(destinationDir), { recursive: true });
10541
- const tempDir = mkdtempSync4(join24(dirname11(destinationDir), ".plan-v2-materialize-"));
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 = join24(tempDir, ...artifact.path.split("/"));
10545
- mkdirSync11(dirname11(destinationPath), { recursive: true });
10546
- copyFileSync7(sourcePath, destinationPath);
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
- writeFileSync8(
10550
- join24(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
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
- renameWithRetry8(tempDir, destinationDir);
10866
+ renameWithRetry9(tempDir, destinationDir);
10559
10867
  } catch (error) {
10560
- rmSync10(tempDir, { recursive: true, force: true });
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 ? join24(destinationDir, audioArtifact.path) : null : null
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 = join24(planDir, PLAN_V2_MATERIALIZATION_MARKER);
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 = join24(planDir, ...artifact.path.split("/"));
10611
- if (!existsSync17(path) || statSync6(path).size !== artifact.sizeBytes) {
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 rmSync13 } from "node:fs";
10932
+ import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
10625
10933
  import { tmpdir as tmpdir2 } from "node:os";
10626
- import { join as join27 } from "node:path";
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 mkdirSync12,
10940
+ mkdirSync as mkdirSync13,
10633
10941
  mkdtempSync as mkdtempSync5,
10634
- readFileSync as readFileSync10,
10942
+ readFileSync as readFileSync11,
10635
10943
  readdirSync as readdirSync8,
10636
- rmSync as rmSync11,
10637
- statSync as statSync7,
10638
- writeFileSync as writeFileSync9
10944
+ rmSync as rmSync12,
10945
+ statSync as statSync8,
10946
+ writeFileSync as writeFileSync10
10639
10947
  } from "node:fs";
10640
- import { dirname as dirname12, join as join25 } from "node:path";
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 runFfmpeg7,
10956
+ runFfmpeg as runFfmpeg8,
10649
10957
  appendSoftwareEncoderThreadsArg,
10650
- renameWithRetry as renameWithRetry9
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: statSync7(outputPath).size
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(dirname12(outputPath))) {
10682
- mkdirSync12(dirname12(outputPath), { recursive: true });
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 = join25(workDir, `output.${format}`);
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
- renameWithRetry9(finalPath, outputPath);
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
- rmSync11(workDir, { recursive: true, force: true });
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) ? statSync7(outputPath).size : 0;
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 = join25(planDir, "plan.json");
10733
- const chunksJsonPath = join25(planDir, "meta", "chunks.json");
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(readFileSync10(planJsonPath, "utf-8"));
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(readFileSync10(chunksJsonPath, "utf-8"));
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 = join25(ctx.workDir, `cfr.${ctx.format}`);
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 = join25(ctx.workDir, "audio-padded.m4a");
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 = join25(workDir, "concat-list.txt");
11156
+ const listPath = join26(workDir, "concat-list.txt");
10849
11157
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
10850
- writeFileSync9(listPath, `${body}
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 runFfmpeg7(args, { signal, timeout });
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(readFileSync10(join25(planDir, "plan.json"), "utf-8"));
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 = join25(planDir, "meta", "encoder.json");
11198
+ const encoderJsonPath = join26(planDir, "meta", "encoder.json");
10891
11199
  if (!existsSync18(encoderJsonPath)) return {};
10892
- return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
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 = join25(planDir, "meta", "encoder.json");
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(readFileSync10(encoderJsonPath, "utf-8"));
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)) rmSync11(outputPath, { recursive: true, force: true });
10914
- mkdirSync12(outputPath, { recursive: true });
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 (!statSync7(chunkDir).isDirectory()) {
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 = join25(outputPath, formatExportFrameName(globalIdx, "png"));
10928
- cpSync3(join25(chunkDir, frame), dst);
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 = join25(outputPath, MIXED_AUDIO_FILENAME4);
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 += statSync7(join25(outputPath, name)).size;
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 mkdirSync13, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync12, writeFileSync as writeFileSync10 } from "node:fs";
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 join26 } from "node:path";
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(readFileSync11(outputPath));
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(readFileSync11(join26(outputPath, name)))}`
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 = join26(planDir, "plan.json");
11100
- const encoderJsonPath = join26(planDir, "meta", "encoder.json");
11101
- const chunksJsonPath = join26(planDir, "meta", "chunks.json");
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(readFileSync11(planJsonPath, "utf-8"));
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(readFileSync11(encoderJsonPath, "utf-8"));
11126
- const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11127
- const videosJsonPath = join26(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
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(readFileSync11(videosJsonPath, "utf-8")));
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 = join26(planDir, "compiled");
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
- mkdirSync13(workDir, { recursive: true });
11217
- const framesDir = join26(workDir, "captured-frames");
11218
- mkdirSync13(framesDir, { recursive: true });
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: readFileSync11(join26(compiledDir, "index.html"), "utf-8"),
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)) mkdirSync13(outputChunkPath, { recursive: true });
11679
+ if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
11372
11680
  } else {
11373
- const outDir = join26(outputChunkPath, "..");
11374
- if (!existsSync19(outDir)) mkdirSync13(outDir, { recursive: true });
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
- rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11486
- mkdirSync13(framesDir, { recursive: true });
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
- rmSync12(videoOnlyPath, { force: true });
11529
- rmSync12(audioScratchPath, { force: true });
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
- writeFileSync10(perfPath, `${JSON.stringify(perfPayload, null, 2)}
11884
+ writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
11577
11885
  `, "utf-8");
11578
11886
  try {
11579
- rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11580
- rmSync12(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
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(join27(tmpdir2(), "hf-plan-v2-chunk-"));
11610
- const materializedPlanDir = join27(workRoot, "plan");
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
- rmSync13(workRoot, { recursive: true, force: true });
11923
+ rmSync14(workRoot, { recursive: true, force: true });
11616
11924
  }
11617
11925
  }
11618
11926
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
11619
- const workRoot = mkdtempSync6(join27(tmpdir2(), "hf-plan-v2-assembler-"));
11620
- const materializedPlanDir = join27(workRoot, "plan");
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
- rmSync13(workRoot, { recursive: true, force: true });
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 closeSync4, openSync as openSync4, readdirSync as readdirSync10, readSync as readSync3, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
11885
- import { createHash as createHash6 } from "node:crypto";
11886
- import { join as join28 } from "node:path";
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 = join28(dir, name);
12203
+ const full = join29(dir, name);
11896
12204
  const relPath = rel === "" ? name : `${rel}/${name}`;
11897
12205
  let stat;
11898
12206
  try {
11899
- stat = statSync8(full);
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 = createHash6("sha256").update(SITE_ID_SCHEME);
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 = openSync4(join28(projectDir, rel), "r");
12233
+ const fd = openSync5(join29(projectDir, rel), "r");
11926
12234
  try {
11927
12235
  let read;
11928
- while ((read = readSync3(fd, chunk, 0, chunk.length, null)) > 0) {
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
- closeSync4(fd);
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 runFfmpeg8 } from "@hyperframes/engine";
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 runFfmpeg8(
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 runFfmpeg8(["-version"], { signal }));
12272
+ var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
11965
12273
 
11966
12274
  // src/services/distributed/probeFacts.ts
11967
- import { writeFileSync as writeFileSync11 } from "node:fs";
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
- writeFileSync11(outputPath, await buildProbeFactsText(inputPath, signal));
12284
+ writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
11977
12285
  return "built";
11978
12286
  } catch (error) {
11979
12287
  if (signal?.aborted) throw error;