@hyperframes/producer 0.8.204 → 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,
@@ -1828,6 +1842,8 @@ import {
1828
1842
  readDeclaredDefaults,
1829
1843
  parseHostVariableValues,
1830
1844
  headStyleRuns,
1845
+ isAlwaysAppliedStyle,
1846
+ dedupeFontFaceRules,
1831
1847
  inlineScriptRuns,
1832
1848
  parsesAsScript,
1833
1849
  styleElementsFor,
@@ -2064,7 +2080,7 @@ function readLoopOverride(el) {
2064
2080
  }
2065
2081
  return null;
2066
2082
  }
2067
- function resolveGifSourcePath(src, options) {
2083
+ function resolveLocalImageSourcePath(src, options) {
2068
2084
  const trimmed = src.trim();
2069
2085
  if (!trimmed || trimmed.startsWith("data:")) return null;
2070
2086
  const { basePath } = splitUrlSuffix(trimmed);
@@ -2120,12 +2136,12 @@ function resolveClipWindowSeconds(img, compositionDuration) {
2120
2136
  }
2121
2137
  return null;
2122
2138
  }
2123
- function resolvePreparedPlayback(metadata, loop, windowSeconds) {
2139
+ function resolvePreparedPlayback(metadata, loopOverride, windowSeconds) {
2124
2140
  const gifDuration = metadata.durationSeconds > 0 ? metadata.durationSeconds : null;
2125
2141
  let loopIterations = 1;
2126
- if (loop) {
2142
+ if (resolveLoop(metadata, loopOverride)) {
2127
2143
  loopIterations = windowSeconds != null && gifDuration != null ? Math.min(MAX_LOOP_ITERATIONS, Math.max(1, Math.ceil(windowSeconds / gifDuration))) : 1;
2128
- } else if (metadata.loopCount != null && metadata.loopCount > 1) {
2144
+ } else if (loopOverride == null && metadata.loopCount != null && metadata.loopCount > 1) {
2129
2145
  loopIterations = Math.min(MAX_LOOP_ITERATIONS, metadata.loopCount);
2130
2146
  }
2131
2147
  let padSeconds = 0;
@@ -2287,14 +2303,19 @@ async function prepareAnimatedGifInputs(html, options) {
2287
2303
  const img = images[i];
2288
2304
  const src = (img.getAttribute("src") || "").trim();
2289
2305
  if (!hasGifExtension(src)) continue;
2290
- const sourcePath = resolveGifSourcePath(src, options);
2306
+ const sourcePath = resolveLocalImageSourcePath(src, options);
2291
2307
  if (!sourcePath) continue;
2292
2308
  const bytes = readFileSync2(sourcePath);
2293
2309
  const metadata = parseAnimatedGifMetadata(bytes);
2294
2310
  if (!metadata?.animated) continue;
2295
- const loop = resolveLoop(metadata, readLoopOverride(img));
2311
+ const loopOverride = readLoopOverride(img);
2312
+ const loop = resolveLoop(metadata, loopOverride);
2296
2313
  const windowSeconds = resolveClipWindowSeconds(img, compositionDuration);
2297
- const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
2314
+ const { loopIterations, padSeconds } = resolvePreparedPlayback(
2315
+ metadata,
2316
+ loopOverride,
2317
+ windowSeconds
2318
+ );
2298
2319
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
2299
2320
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
2300
2321
  const cachePath = join4(cacheDir, filename);
@@ -2330,19 +2351,74 @@ async function prepareAnimatedGifInputs(html, options) {
2330
2351
  };
2331
2352
  }
2332
2353
 
2333
- // src/services/htmlCompiler.ts
2334
- import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
2335
- import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
2336
-
2337
- // src/services/assetMediaType.ts
2338
- import { existsSync as existsSync5 } from "node:fs";
2354
+ // src/services/heifImagePrep.ts
2355
+ import { createHash as createHash3 } from "node:crypto";
2339
2356
  import {
2340
- fingerprintElementId,
2341
- isNotMediaPayload,
2342
- NotMediaPayloadError,
2343
- probeMediaProfile,
2344
- resolveProjectRelativeSrc as resolveProjectRelativeSrc2
2345
- } 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
+ }
2346
2422
 
2347
2423
  // src/utils/semaphore.ts
2348
2424
  var Semaphore = class {
@@ -2357,10 +2433,10 @@ var Semaphore = class {
2357
2433
  this.active++;
2358
2434
  return () => this.release();
2359
2435
  }
2360
- return new Promise((resolve9) => {
2436
+ return new Promise((resolve10) => {
2361
2437
  this.queue.push(() => {
2362
2438
  this.active++;
2363
- resolve9(() => this.release());
2439
+ resolve10(() => this.release());
2364
2440
  });
2365
2441
  });
2366
2442
  }
@@ -2391,7 +2467,229 @@ async function withMediaProbeSlot(operation) {
2391
2467
  }
2392
2468
  }
2393
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
+
2394
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";
2395
2693
  var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
2396
2694
  var AssetMediaTypeMismatchError = class extends Error {
2397
2695
  code = ASSET_MEDIA_TYPE_MISMATCH;
@@ -2501,7 +2799,7 @@ function logRemoteDownloadTelemetry(event) {
2501
2799
  }
2502
2800
  var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
2503
2801
  function parseSubCompHtmlForValidity(html) {
2504
- return parseHTML4(html).document;
2802
+ return parseHTML5(html).document;
2505
2803
  }
2506
2804
  function endsWithSourceUrl(code) {
2507
2805
  const trimmed = code.trimEnd();
@@ -2509,7 +2807,7 @@ function endsWithSourceUrl(code) {
2509
2807
  return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
2510
2808
  }
2511
2809
  function prepareCompositionScripts(html) {
2512
- const { document: document2 } = parseHTML4(html);
2810
+ const { document: document2 } = parseHTML5(html);
2513
2811
  for (const el of document2.querySelectorAll("script:not([src])")) {
2514
2812
  const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
2515
2813
  const runsAsScript = isModule || isJavaScriptType(el);
@@ -2546,14 +2844,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
2546
2844
  }
2547
2845
  };
2548
2846
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
2549
- const { document: document2 } = parseHTML4(html);
2847
+ const { document: document2 } = parseHTML5(html);
2550
2848
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
2551
2849
  const problems = [];
2552
2850
  for (const el of hosts) {
2553
2851
  const srcPath = el.getAttribute("data-composition-src");
2554
2852
  if (!srcPath) continue;
2555
2853
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
2556
- const filePath = resolve5(projectDir, srcPath);
2854
+ const filePath = resolve6(projectDir, srcPath);
2557
2855
  if (visited.has(filePath)) continue;
2558
2856
  const read = readProjectFile(filePath);
2559
2857
  if (read.kind === "missing") {
@@ -2609,7 +2907,7 @@ function runtimeRoot(document2) {
2609
2907
  }
2610
2908
  function detectRenderModeHints(html, pageMotionBlur) {
2611
2909
  const reasons = [];
2612
- const { document: document2 } = parseHTML4(html);
2910
+ const { document: document2 } = parseHTML5(html);
2613
2911
  if (document2.querySelector("canvas[layoutsubtree]")) {
2614
2912
  reasons.push({
2615
2913
  code: "htmlInCanvas",
@@ -2651,7 +2949,7 @@ function detectThreeDTransformUsage(html) {
2651
2949
  }
2652
2950
  var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
2653
2951
  function detectAncestorBackgroundImage(html) {
2654
- const { document: document2 } = parseHTML4(html);
2952
+ const { document: document2 } = parseHTML5(html);
2655
2953
  const root = document2.querySelector("[data-composition-id]");
2656
2954
  if (!root) return false;
2657
2955
  const ancestors = [];
@@ -2698,7 +2996,7 @@ function detectShaderTransitionUsage(html) {
2698
2996
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
2699
2997
  let filePath = src;
2700
2998
  if (isHttpUrl(src)) {
2701
- if (!existsSync6(downloadDir)) mkdirSync3(downloadDir, { recursive: true });
2999
+ if (!existsSync6(downloadDir)) mkdirSync4(downloadDir, { recursive: true });
2702
3000
  try {
2703
3001
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
2704
3002
  onTelemetry: logRemoteDownloadTelemetry
@@ -2708,7 +3006,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
2708
3006
  }
2709
3007
  } else {
2710
3008
  filePath = decodedUrlPath(src);
2711
- if (!filePath.startsWith("/")) filePath = join5(baseDir, filePath);
3009
+ if (!filePath.startsWith("/")) filePath = join6(baseDir, filePath);
2712
3010
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
2713
3011
  }
2714
3012
  if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
@@ -2765,7 +3063,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
2765
3063
  function markInferredVariableMediaDurations(html, unresolvedMedia) {
2766
3064
  const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
2767
3065
  if (unresolvedIds.size === 0) return html;
2768
- const { document: document2 } = parseHTML4(html);
3066
+ const { document: document2 } = parseHTML5(html);
2769
3067
  let changed = false;
2770
3068
  for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
2771
3069
  if (!unresolvedIds.has(element.id)) continue;
@@ -2836,13 +3134,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
2836
3134
  }
2837
3135
  async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
2838
3136
  const subCompositions = /* @__PURE__ */ new Map();
2839
- const { document: document2 } = parseHTML4(html);
3137
+ const { document: document2 } = parseHTML5(html);
2840
3138
  const compEls = document2.querySelectorAll("[data-composition-src]");
2841
3139
  const workItems = [];
2842
3140
  for (const el of compEls) {
2843
3141
  const srcPath = el.getAttribute("data-composition-src");
2844
3142
  if (!srcPath) continue;
2845
- const filePath = resolve5(projectDir, srcPath);
3143
+ const filePath = resolve6(projectDir, srcPath);
2846
3144
  if (visited.has(filePath)) {
2847
3145
  continue;
2848
3146
  }
@@ -2859,7 +3157,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
2859
3157
  workItems.map(async (item) => {
2860
3158
  const { html: compiledSub } = await compileHtmlFile(
2861
3159
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
2862
- dirname4(item.filePath),
3160
+ dirname5(item.filePath),
2863
3161
  downloadDir,
2864
3162
  void 0,
2865
3163
  urlByAbsentLocalPath
@@ -2887,7 +3185,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
2887
3185
  return { subCompositions };
2888
3186
  }
2889
3187
  function promoteCssImportsToLinkTags(html) {
2890
- const { document: document2 } = parseHTML4(html);
3188
+ const { document: document2 } = parseHTML5(html);
2891
3189
  const head = document2.querySelector("head");
2892
3190
  if (!head) return html;
2893
3191
  const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
@@ -2956,10 +3254,15 @@ var ProducerHostIdentityMap = class extends Map {
2956
3254
  }
2957
3255
  };
2958
3256
  function coalesceHeadStylesAndBodyScripts(html) {
2959
- const { document: document2 } = parseHTML4(html);
3257
+ const { document: document2 } = parseHTML5(html);
2960
3258
  const head = document2.querySelector("head");
2961
3259
  const body = document2.querySelector("body");
2962
3260
  if (!head && !body) return html;
3261
+ const liveStyles = [...document2.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
3262
+ const dedupedStyles = dedupeFontFaceRules(liveStyles.map((el) => el.textContent || ""));
3263
+ liveStyles.forEach((el, i) => {
3264
+ el.textContent = dedupedStyles[i] ?? "";
3265
+ });
2963
3266
  const styleEls = head ? Array.from(head.querySelectorAll("style")) : [];
2964
3267
  const importRe = /@import\s+url\([^)]*\)\s*;|@import\s+["'][^"']+["']\s*;/gi;
2965
3268
  for (const run of styleEls.length > 1 ? headStyleRuns(styleEls) : []) {
@@ -3002,7 +3305,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
3002
3305
  return document2.toString();
3003
3306
  }
3004
3307
  function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
3005
- const { document: document2 } = parseHTML4(html);
3308
+ const { document: document2 } = parseHTML5(html);
3006
3309
  const head = document2.querySelector("head");
3007
3310
  const body = document2.querySelector("body");
3008
3311
  const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
@@ -3031,17 +3334,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
3031
3334
  resolveHtml: (srcPath) => {
3032
3335
  let compHtml = subCompositions.get(srcPath) || null;
3033
3336
  if (!compHtml) {
3034
- const read = readProjectFile(resolve5(projectDir, srcPath));
3337
+ const read = readProjectFile(resolve6(projectDir, srcPath));
3035
3338
  if (read.kind === "file") {
3036
3339
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
3037
3340
  }
3038
3341
  }
3039
3342
  return compHtml;
3040
3343
  },
3041
- parseHtml: (htmlStr) => parseHTML4(htmlStr).document,
3344
+ parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
3042
3345
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
3043
3346
  // against its own directory, project-root refs stay as authored.
3044
- assetExists: (path) => existsSync6(resolve5(projectDir, path)),
3347
+ assetExists: (path) => existsSync6(resolve6(projectDir, path)),
3045
3348
  // Preserve the authored root wrapper as a child of the host, matching
3046
3349
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
3047
3350
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -3142,7 +3445,7 @@ ${html}
3142
3445
  </html>`;
3143
3446
  }
3144
3447
  function injectTextRenderingRule(html) {
3145
- const { document: document2 } = parseHTML4(html);
3448
+ const { document: document2 } = parseHTML5(html);
3146
3449
  const head = document2.querySelector("head");
3147
3450
  if (!head) return html;
3148
3451
  if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
@@ -3166,7 +3469,7 @@ function matchesScriptIntegrity(bytes, metadata) {
3166
3469
  (alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
3167
3470
  );
3168
3471
  if (!strongest) return true;
3169
- const actual = createHash3(strongest).update(bytes).digest();
3472
+ const actual = createHash4(strongest).update(bytes).digest();
3170
3473
  return hashes.some(
3171
3474
  (hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
3172
3475
  );
@@ -3174,7 +3477,7 @@ function matchesScriptIntegrity(bytes, metadata) {
3174
3477
  async function inlineExternalScripts(html) {
3175
3478
  const fullHtml = ensureFullDocument(html);
3176
3479
  const wrappedFragment = fullHtml !== html;
3177
- const { document: document2 } = parseHTML4(fullHtml);
3480
+ const { document: document2 } = parseHTML5(fullHtml);
3178
3481
  const scripts = document2.querySelectorAll("script[src]");
3179
3482
  const externalScripts = [];
3180
3483
  for (const el of scripts) {
@@ -3223,12 +3526,12 @@ ${safeText}
3223
3526
  return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
3224
3527
  }
3225
3528
  function collectExternalAssets(html, projectDir) {
3226
- const absProjectDir = resolve5(projectDir);
3529
+ const absProjectDir = resolve6(projectDir);
3227
3530
  const externalAssets = /* @__PURE__ */ new Map();
3228
3531
  function processPath(rawPath) {
3229
3532
  const trimmed = rawPath.trim();
3230
3533
  if (isNonRelativeUrl(trimmed)) return null;
3231
- const absPath = resolve5(absProjectDir, trimmed);
3534
+ const absPath = resolve6(absProjectDir, trimmed);
3232
3535
  if (isPathInside2(absPath, absProjectDir)) {
3233
3536
  return null;
3234
3537
  }
@@ -3237,7 +3540,7 @@ function collectExternalAssets(html, projectDir) {
3237
3540
  externalAssets.set(safeKey, absPath);
3238
3541
  return safeKey;
3239
3542
  }
3240
- const { document: document2 } = parseHTML4(html);
3543
+ const { document: document2 } = parseHTML5(html);
3241
3544
  for (const el of document2.querySelectorAll("[src], [href]")) {
3242
3545
  for (const attr of ["src", "href"]) {
3243
3546
  const val = (el.getAttribute(attr) || "").trim();
@@ -3282,7 +3585,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
3282
3585
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
3283
3586
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
3284
3587
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
3285
- if (!existsSync6(remoteDir)) mkdirSync3(remoteDir, { recursive: true });
3588
+ if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3286
3589
  const urlToLocal = /* @__PURE__ */ new Map();
3287
3590
  await Promise.all(
3288
3591
  [...urlSet].map(async (url) => {
@@ -3329,7 +3632,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
3329
3632
  return downloadAndRewriteUrls(
3330
3633
  urlSet,
3331
3634
  html,
3332
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
3635
+ join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3333
3636
  "Remote media download failed",
3334
3637
  "Localized remote media source(s)"
3335
3638
  );
@@ -3344,7 +3647,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
3344
3647
  return downloadAndRewriteUrls(
3345
3648
  urlSet,
3346
3649
  html,
3347
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
3650
+ join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3348
3651
  "Remote image download failed",
3349
3652
  "Localized remote image source(s)"
3350
3653
  );
@@ -3360,7 +3663,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
3360
3663
  return downloadAndRewriteUrls(
3361
3664
  urlSet,
3362
3665
  html,
3363
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
3666
+ join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3364
3667
  "Remote background-image download failed",
3365
3668
  "Localized remote background-image(s)",
3366
3669
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -3495,7 +3798,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
3495
3798
  return downloadAndRewriteUrls(
3496
3799
  urlSet,
3497
3800
  processed,
3498
- join5(downloadDir, REMOTE_MEDIA_SUBDIR),
3801
+ join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3499
3802
  "Remote font download failed",
3500
3803
  "Localized remote font face(s)",
3501
3804
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -3540,7 +3843,7 @@ async function embedLocalFontFaces(html, projectDir) {
3540
3843
  while ((urlMatch = urlRe.exec(block)) !== null) {
3541
3844
  const localPath = urlMatch[1];
3542
3845
  if (!localPath || embeddedPaths.has(localPath)) continue;
3543
- const absPath = localPath.startsWith("/") ? localPath : resolve5(projectDir, localPath);
3846
+ const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
3544
3847
  if (!isPathInside2(absPath, projectDir)) continue;
3545
3848
  const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
3546
3849
  try {
@@ -3589,7 +3892,7 @@ function renderGsapDist() {
3589
3892
  }
3590
3893
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
3591
3894
  let gsapDist;
3592
- const exists = (path) => existsSync6(resolve5(projectDir, path));
3895
+ const exists = (path) => existsSync6(resolve6(projectDir, path));
3593
3896
  return html.replace(
3594
3897
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
3595
3898
  (full, prefix, src, file, suffix) => {
@@ -3605,8 +3908,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
3605
3908
  if (!isPathInside2(htmlPath, projectDir)) return html;
3606
3909
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
3607
3910
  if (!entryPath.includes("/")) return html;
3608
- const { document: document2 } = parseHTML4(html);
3609
- const assetExists = (path) => existsSync6(resolve5(projectDir, path));
3911
+ const { document: document2 } = parseHTML5(html);
3912
+ const assetExists = (path) => existsSync6(resolve6(projectDir, path));
3610
3913
  rewriteAssetPaths(
3611
3914
  document2.querySelectorAll("[src], [href]"),
3612
3915
  entryPath,
@@ -3628,7 +3931,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
3628
3931
  }
3629
3932
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
3630
3933
  const entryHtml = rebaseDirectEntryAssetPaths(
3631
- readFileSync3(htmlPath, "utf-8"),
3934
+ readFileSync4(htmlPath, "utf-8"),
3632
3935
  projectDir,
3633
3936
  htmlPath
3634
3937
  );
@@ -3698,12 +4001,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3698
4001
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
3699
4002
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
3700
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
+ }
3701
4018
  const gifSourceAssets = new Map(remoteImageAssets);
3702
4019
  const {
3703
4020
  html: htmlWithPreparedGifs,
3704
4021
  preparedAssets: preparedGifAssets,
3705
4022
  preparedGifs
3706
- } = await prepareAnimatedGifInputs(htmlWithLocalizedFonts, {
4023
+ } = await prepareAnimatedGifInputs(htmlWithJpegs, {
3707
4024
  projectDir,
3708
4025
  downloadDir,
3709
4026
  cacheDir: options.animatedGifCacheDir,
@@ -3730,11 +4047,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3730
4047
  for (const [relPath, absPath] of preparedGifAssets) {
3731
4048
  externalAssets.set(relPath, absPath);
3732
4049
  }
4050
+ for (const [relPath, absPath] of preparedHeifAssets) {
4051
+ externalAssets.set(relPath, absPath);
4052
+ }
3733
4053
  const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
3734
4054
  const masterFxChain = parseMasterFxChain(html);
3735
4055
  for (const video of videos) {
3736
4056
  if (isHttpUrl(video.src)) continue;
3737
- const videoPath = resolve5(projectDir, video.src);
4057
+ const videoPath = resolve6(projectDir, video.src);
3738
4058
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
3739
4059
  (async () => {
3740
4060
  const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
@@ -3754,7 +4074,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3754
4074
  })().catch(() => {
3755
4075
  });
3756
4076
  }
3757
- const { document: document2 } = parseHTML4(html);
4077
+ const { document: document2 } = parseHTML5(html);
3758
4078
  const rootEl = document2.querySelector("[data-composition-id]");
3759
4079
  const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
3760
4080
  const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
@@ -4161,7 +4481,7 @@ async function runCompileStage(input) {
4161
4481
  abortSignal
4162
4482
  } = input;
4163
4483
  const compileStart = Date.now();
4164
- const compiled = await compileForRender(projectDir, htmlPath, join6(workDir, "downloads"), {
4484
+ const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
4165
4485
  log,
4166
4486
  failClosedFontFetch: failClosedFontFetch === true,
4167
4487
  allowSystemFontCapture,
@@ -4169,7 +4489,7 @@ async function runCompileStage(input) {
4169
4489
  variables: input.variables,
4170
4490
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
4171
4491
  pageMotionBlur: input.pageMotionBlur,
4172
- animatedGifCacheDir: cfg.extractCacheDir ? join6(cfg.extractCacheDir, "animated-gif") : void 0,
4492
+ animatedGifCacheDir: cfg.extractCacheDir ? join7(cfg.extractCacheDir, "animated-gif") : void 0,
4173
4493
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
4174
4494
  });
4175
4495
  assertNotAborted();
@@ -4225,7 +4545,7 @@ async function runCompileStage(input) {
4225
4545
  };
4226
4546
  await preflightCompositionAssetMediaTypes({
4227
4547
  projectDir,
4228
- compiledDir: join6(workDir, "compiled"),
4548
+ compiledDir: join7(workDir, "compiled"),
4229
4549
  composition,
4230
4550
  signal: abortSignal
4231
4551
  });
@@ -4270,8 +4590,8 @@ async function runCompileStage(input) {
4270
4590
  }
4271
4591
 
4272
4592
  // src/services/render/stages/probeStage.ts
4273
- import { join as join7 } from "node:path";
4274
- import { parseHTML as parseHTML5 } from "linkedom";
4593
+ import { join as join8 } from "node:path";
4594
+ import { parseHTML as parseHTML6 } from "linkedom";
4275
4595
  import {
4276
4596
  closeCaptureSession as closeCaptureSession4,
4277
4597
  compositionRequiresWebGpu,
@@ -4318,7 +4638,7 @@ function durationToFrameCount(duration, fps) {
4318
4638
  }
4319
4639
  function hasScriptedAudioVolumeAutomation(html, audioCount) {
4320
4640
  if (audioCount <= 0) return false;
4321
- const { document: document2 } = parseHTML5(html);
4641
+ const { document: document2 } = parseHTML6(html);
4322
4642
  const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
4323
4643
  if (!scriptBodies) return false;
4324
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(
@@ -4326,12 +4646,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
4326
4646
  );
4327
4647
  }
4328
4648
  function hasAutoStartVideos(html) {
4329
- const { document: document2 } = parseHTML5(html);
4649
+ const { document: document2 } = parseHTML6(html);
4330
4650
  return document2.querySelector("video[data-hf-auto-start]") !== null;
4331
4651
  }
4332
4652
  function hasVariableBoundMedia(html, variables) {
4333
4653
  if (!variables || Object.keys(variables).length === 0) return false;
4334
- const { document: document2 } = parseHTML5(html);
4654
+ const { document: document2 } = parseHTML6(html);
4335
4655
  return Array.from(
4336
4656
  document2.querySelectorAll(
4337
4657
  "img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
@@ -4342,7 +4662,7 @@ function hasVariableBoundMedia(html, variables) {
4342
4662
  });
4343
4663
  }
4344
4664
  function authoredMediaStarts(html) {
4345
- const { document: document2 } = parseHTML5(html);
4665
+ const { document: document2 } = parseHTML6(html);
4346
4666
  const starts = /* @__PURE__ */ new Map();
4347
4667
  for (const media of document2.querySelectorAll("video, audio")) {
4348
4668
  const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
@@ -4379,7 +4699,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
4379
4699
  return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
4380
4700
  }
4381
4701
  function hasRuntimeMediaChanges(html) {
4382
- const { document: document2 } = parseHTML5(html);
4702
+ const { document: document2 } = parseHTML6(html);
4383
4703
  const scriptBodies = [...document2.querySelectorAll("script")].map(
4384
4704
  (script) => script.textContent ?? ""
4385
4705
  );
@@ -4462,7 +4782,7 @@ async function runProbeStage(input) {
4462
4782
  });
4463
4783
  fileServer = await createFileServer({
4464
4784
  projectDir,
4465
- compiledDir: join7(workDir, "compiled"),
4785
+ compiledDir: join8(workDir, "compiled"),
4466
4786
  port: 0,
4467
4787
  preHeadScripts: [VIRTUAL_TIME_SHIM],
4468
4788
  fps: job.config.fps
@@ -4491,7 +4811,7 @@ async function runProbeStage(input) {
4491
4811
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
4492
4812
  probeSession = await createCaptureSession4(
4493
4813
  fileServer.url,
4494
- join7(workDir, "probe"),
4814
+ join8(workDir, "probe"),
4495
4815
  captureOpts,
4496
4816
  null,
4497
4817
  probeCfg
@@ -4576,7 +4896,7 @@ async function runProbeStage(input) {
4576
4896
  });
4577
4897
  probeSession = await createCaptureSession4(
4578
4898
  fileServer.url,
4579
- join7(workDir, "probe-screenshot"),
4899
+ join8(workDir, "probe-screenshot"),
4580
4900
  captureOpts,
4581
4901
  null,
4582
4902
  { ...probeCfg, forceScreenshot: true }
@@ -4612,7 +4932,7 @@ async function runProbeStage(input) {
4612
4932
  compiled,
4613
4933
  resolutions,
4614
4934
  projectDir,
4615
- join7(workDir, "downloads")
4935
+ join8(workDir, "downloads")
4616
4936
  );
4617
4937
  assertNotAborted();
4618
4938
  composition.videos = compiled.videos;
@@ -4803,7 +5123,7 @@ async function runProbeStage(input) {
4803
5123
  }
4804
5124
  await preflightCompositionAssetMediaTypes({
4805
5125
  projectDir,
4806
- compiledDir: join7(workDir, "compiled"),
5126
+ compiledDir: join8(workDir, "compiled"),
4807
5127
  composition,
4808
5128
  signal: abortSignal
4809
5129
  });
@@ -4968,7 +5288,7 @@ function validateDistributedDuration(input) {
4968
5288
 
4969
5289
  // src/services/render/stages/extractVideosStage.ts
4970
5290
  import { existsSync as existsSync7 } from "node:fs";
4971
- import { join as join8 } from "node:path";
5291
+ import { join as join9 } from "node:path";
4972
5292
  import {
4973
5293
  classifyVideoExtractionError,
4974
5294
  createFrameLookupTable,
@@ -5288,14 +5608,14 @@ async function runExtractVideosStage(input) {
5288
5608
  // because short boundary counts can differ by one frame.
5289
5609
  {
5290
5610
  fps: job.config.fps,
5291
- outputDir: join8(compiledDir, "__hyperframes_video_frames"),
5611
+ outputDir: join9(compiledDir, "__hyperframes_video_frames"),
5292
5612
  format: job.config.videoFrameFormat ?? "auto",
5293
5613
  toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
5294
5614
  timelineEnd: composition.duration,
5295
5615
  maxTransientRetries: extractionPolicy.maxTransientRetries,
5296
5616
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
5297
5617
  deferRangeExtraction,
5298
- contentKeyedDirs: [join8(compiledDir, REMOTE_MEDIA_SUBDIR)],
5618
+ contentKeyedDirs: [join9(compiledDir, REMOTE_MEDIA_SUBDIR)],
5299
5619
  urlByAbsentLocalPath
5300
5620
  },
5301
5621
  abortSignal,
@@ -5389,8 +5709,8 @@ async function appendVideoAudioFromProbe(input) {
5389
5709
  }
5390
5710
 
5391
5711
  // src/services/render/stages/audioStage.ts
5392
- import { join as join10 } from "node:path";
5393
- import { rmSync as rmSync4 } from "node:fs";
5712
+ import { join as join11 } from "node:path";
5713
+ import { rmSync as rmSync5 } from "node:fs";
5394
5714
  import {
5395
5715
  MIXED_AUDIO_FILENAME,
5396
5716
  audioFailureDiagnostics,
@@ -5399,8 +5719,8 @@ import {
5399
5719
 
5400
5720
  // src/services/render/audioPadTrim.ts
5401
5721
  import { spawn } from "node:child_process";
5402
- import { mkdtempSync, rmSync as rmSync3 } from "node:fs";
5403
- 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";
5404
5724
  import {
5405
5725
  DEFAULT_CONFIG,
5406
5726
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -5408,8 +5728,8 @@ import {
5408
5728
  extractAudioMetadata as extractAudioMetadata3,
5409
5729
  getFfprobeBinary,
5410
5730
  ManagedChildProcess,
5411
- renameWithRetry as renameWithRetry4,
5412
- runFfmpeg as runFfmpeg4,
5731
+ renameWithRetry as renameWithRetry5,
5732
+ runFfmpeg as runFfmpeg5,
5413
5733
  trackChildProcess
5414
5734
  } from "@hyperframes/engine";
5415
5735
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
@@ -5419,7 +5739,7 @@ import {
5419
5739
  aacSegmentCount,
5420
5740
  describeFfmpegFailure,
5421
5741
  extractAudioMetadata as extractAudioMetadata2,
5422
- runFfmpeg as runFfmpeg3
5742
+ runFfmpeg as runFfmpeg4
5423
5743
  } from "@hyperframes/engine";
5424
5744
  var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
5425
5745
  function checkedDbfs(value) {
@@ -5443,7 +5763,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
5443
5763
  else laterBlocks = Math.max(laterBlocks, block);
5444
5764
  }
5445
5765
  };
5446
- const result = await runFfmpeg3(
5766
+ const result = await runFfmpeg4(
5447
5767
  ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
5448
5768
  { signal, timeout: timeoutMs, onStderr: readLines }
5449
5769
  );
@@ -5603,7 +5923,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
5603
5923
  error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
5604
5924
  };
5605
5925
  } finally {
5606
- for (const path of plan2.cleanupPaths) rmSync3(path, { force: true });
5926
+ for (const path of plan2.cleanupPaths) rmSync4(path, { force: true });
5607
5927
  }
5608
5928
  let loweredDb = 0;
5609
5929
  if (probeTruePeak) {
@@ -5650,8 +5970,8 @@ async function constrainAacTruePeak(input) {
5650
5970
  async function enforceAacTruePeak(input) {
5651
5971
  let scratchDir;
5652
5972
  try {
5653
- scratchDir = mkdtempSync(join9(dirname5(input.audioPath), ".true-peak-"));
5654
- const correctedPath = join9(scratchDir, "audio.m4a");
5973
+ scratchDir = mkdtempSync(join10(dirname6(input.audioPath), ".true-peak-"));
5974
+ const correctedPath = join10(scratchDir, "audio.m4a");
5655
5975
  const measurements = [];
5656
5976
  let attenuationDb = 0;
5657
5977
  let sourcePeakDbfs;
@@ -5666,7 +5986,7 @@ async function enforceAacTruePeak(input) {
5666
5986
  }
5667
5987
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
5668
5988
  if (measuredPath !== correctedPath) return { success: true };
5669
- renameWithRetry4(correctedPath, input.audioPath);
5989
+ renameWithRetry5(correctedPath, input.audioPath);
5670
5990
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
5671
5991
  }
5672
5992
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -5707,7 +6027,7 @@ async function enforceAacTruePeak(input) {
5707
6027
  )}`
5708
6028
  };
5709
6029
  } finally {
5710
- if (scratchDir) rmSync3(scratchDir, { recursive: true, force: true });
6030
+ if (scratchDir) rmSync4(scratchDir, { recursive: true, force: true });
5711
6031
  }
5712
6032
  }
5713
6033
  function failResult(outputPath, target, source, error) {
@@ -5784,7 +6104,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
5784
6104
  };
5785
6105
  }
5786
6106
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
5787
- return toStepResult(await runFfmpeg4(args, { signal, timeout: timeoutMs }), timeoutMs);
6107
+ return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
5788
6108
  }
5789
6109
  function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
5790
6110
  return async (inputPath, outputPath, durationSeconds, filter) => {
@@ -5854,7 +6174,7 @@ function assertAudioNotInterrupted(failures) {
5854
6174
  }
5855
6175
  async function runAudioStage(input) {
5856
6176
  const stageStart = Date.now();
5857
- const pcmSourcePath = join10(input.workDir, "audio-source.wav");
6177
+ const pcmSourcePath = join11(input.workDir, "audio-source.wav");
5858
6178
  const duration = input.totalFrames * input.fps.den / input.fps.num;
5859
6179
  try {
5860
6180
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
@@ -5874,7 +6194,7 @@ async function runAudioStage(input) {
5874
6194
  }
5875
6195
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
5876
6196
  } finally {
5877
- rmSync4(pcmSourcePath, { force: true });
6197
+ rmSync5(pcmSourcePath, { force: true });
5878
6198
  }
5879
6199
  }
5880
6200
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -5892,7 +6212,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5892
6212
  assertNotAborted,
5893
6213
  log
5894
6214
  } = input;
5895
- const audioOutputPath = join10(workDir, MIXED_AUDIO_FILENAME);
6215
+ const audioOutputPath = join11(workDir, MIXED_AUDIO_FILENAME);
5896
6216
  let hasAudio = false;
5897
6217
  let audioError;
5898
6218
  let audioFailures;
@@ -5903,7 +6223,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5903
6223
  audioResult = await processCompositionAudio(
5904
6224
  audios,
5905
6225
  projectDir,
5906
- join10(workDir, "audio-work"),
6226
+ join11(workDir, "audio-work"),
5907
6227
  audioOutputPath,
5908
6228
  duration,
5909
6229
  abortSignal,
@@ -5972,7 +6292,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5972
6292
 
5973
6293
  // src/services/render/stages/captureStage.ts
5974
6294
  import { existsSync as existsSync8, readdirSync, statfsSync } from "node:fs";
5975
- import { join as join11 } from "node:path";
6295
+ import { join as join12 } from "node:path";
5976
6296
  import {
5977
6297
  classifyCaptureFailure as classifyCaptureFailure2,
5978
6298
  frameFileExtension,
@@ -6026,13 +6346,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
6026
6346
  if (!existsSync8(workDir)) return total;
6027
6347
  for (const attempt of readdirSync(workDir)) {
6028
6348
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6029
- total += sampleDirectoryBytes(join11(workDir, attempt));
6349
+ total += sampleDirectoryBytes(join12(workDir, attempt));
6030
6350
  continue;
6031
6351
  }
6032
6352
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
6033
- const attemptDir = join11(workDir, attempt);
6353
+ const attemptDir = join12(workDir, attempt);
6034
6354
  for (const name of readdirSync(attemptDir)) {
6035
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join11(attemptDir, name));
6355
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join12(attemptDir, name));
6036
6356
  }
6037
6357
  }
6038
6358
  return total;
@@ -6358,24 +6678,24 @@ import {
6358
6678
 
6359
6679
  // src/services/render/stages/encodeStage.ts
6360
6680
  import {
6361
- copyFileSync as copyFileSync3,
6681
+ copyFileSync as copyFileSync4,
6362
6682
  existsSync as existsSync9,
6363
- mkdirSync as mkdirSync4,
6683
+ mkdirSync as mkdirSync5,
6364
6684
  readdirSync as readdirSync2,
6365
- readFileSync as readFileSync4,
6366
- rmSync as rmSync5,
6367
- statSync as statSync3,
6368
- writeFileSync as writeFileSync2
6685
+ readFileSync as readFileSync5,
6686
+ rmSync as rmSync6,
6687
+ statSync as statSync4,
6688
+ writeFileSync as writeFileSync3
6369
6689
  } from "node:fs";
6370
6690
  import { readFile as readFile2 } from "node:fs/promises";
6371
- import { dirname as dirname6, join as join12 } from "node:path";
6691
+ import { dirname as dirname7, join as join13 } from "node:path";
6372
6692
  import {
6373
6693
  encodeFramesChunkedConcat,
6374
6694
  encodeFramesFromDir,
6375
6695
  formatFfmpegError,
6376
6696
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
6377
6697
  resolveConfig,
6378
- runFfmpeg as runFfmpeg5,
6698
+ runFfmpeg as runFfmpeg6,
6379
6699
  spawnStreamingEncoder as spawnStreamingEncoder4
6380
6700
  } from "@hyperframes/engine";
6381
6701
  import {
@@ -6451,17 +6771,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
6451
6771
  }
6452
6772
  async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
6453
6773
  const { outputPath } = argsInput;
6454
- const result = await runFfmpeg5(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
6774
+ const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
6455
6775
  if (!result.success) {
6456
6776
  const error = formatFfmpegError(result.exitCode, result.stderr);
6457
6777
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
6458
6778
  }
6459
- const gif = readFileSync4(outputPath);
6779
+ const gif = readFileSync5(outputPath);
6460
6780
  if (!clearGifFramesBeforeNext(gif)) {
6461
6781
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
6462
6782
  return gifEncodeFailure(startTime, outputPath, error);
6463
6783
  }
6464
- writeFileSync2(outputPath, gif);
6784
+ writeFileSync3(outputPath, gif);
6465
6785
  return null;
6466
6786
  }
6467
6787
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -6483,17 +6803,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
6483
6803
  try {
6484
6804
  const run = { signal: input.signal, timeout: input.timeout };
6485
6805
  for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
6486
- const result = await runFfmpeg5(args, run);
6806
+ const result = await runFfmpeg6(args, run);
6487
6807
  if (!result.success) {
6488
6808
  const error = formatFfmpegError(result.exitCode, result.stderr);
6489
6809
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
6490
6810
  }
6491
6811
  }
6492
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync4(outputPath)) !== false) {
6812
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
6493
6813
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
6494
6814
  if (failure) return failure;
6495
6815
  }
6496
- const fileSize = existsSync9(outputPath) ? statSync3(outputPath).size : 0;
6816
+ const fileSize = existsSync9(outputPath) ? statSync4(outputPath).size : 0;
6497
6817
  return {
6498
6818
  success: true,
6499
6819
  outputPath,
@@ -6502,7 +6822,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
6502
6822
  fileSize
6503
6823
  };
6504
6824
  } finally {
6505
- rmSync5(input.palettePath, { force: true });
6825
+ rmSync6(input.palettePath, { force: true });
6506
6826
  }
6507
6827
  }
6508
6828
  function startEncodeProgress(job, stage, onProgress) {
@@ -6574,10 +6894,10 @@ function encodeWhileCapturing(input, totalFrames) {
6574
6894
  const stream = await encoder;
6575
6895
  for (let next = 0; next < totalFrames; next++) {
6576
6896
  while (!landed.has(next) && !captureDone && !signal.aborted) {
6577
- await new Promise((resolve9) => wake = resolve9);
6897
+ await new Promise((resolve10) => wake = resolve10);
6578
6898
  }
6579
6899
  if (signal.aborted) return;
6580
- const mergedPath = join12(input.framesDir, formatCaptureFrameName(next, "jpg"));
6900
+ const mergedPath = join13(input.framesDir, formatCaptureFrameName(next, "jpg"));
6581
6901
  const frame = await readLandedFrame(landed.get(next), mergedPath);
6582
6902
  landed.delete(next);
6583
6903
  if (!await stream.writeFrame(frame)) {
@@ -6652,7 +6972,7 @@ async function runEncodeStage(input) {
6652
6972
  const stage5Start = Date.now();
6653
6973
  if (isPngSequence) {
6654
6974
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
6655
- if (!existsSync9(outputPath)) mkdirSync4(outputPath, { recursive: true });
6975
+ if (!existsSync9(outputPath)) mkdirSync5(outputPath, { recursive: true });
6656
6976
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
6657
6977
  if (captured.length === 0) {
6658
6978
  throw new Error(
@@ -6660,11 +6980,11 @@ async function runEncodeStage(input) {
6660
6980
  );
6661
6981
  }
6662
6982
  captured.forEach((name, i) => {
6663
- const dst = join12(outputPath, formatExportFrameName(i, "png"));
6664
- copyFileSync3(join12(framesDir, name), dst);
6983
+ const dst = join13(outputPath, formatExportFrameName(i, "png"));
6984
+ copyFileSync4(join13(framesDir, name), dst);
6665
6985
  });
6666
6986
  if (hasAudio && audioOutputPath && existsSync9(audioOutputPath)) {
6667
- copyFileSync3(audioOutputPath, join12(outputPath, MIXED_AUDIO_FILENAME2));
6987
+ copyFileSync4(audioOutputPath, join13(outputPath, MIXED_AUDIO_FILENAME2));
6668
6988
  log.info(
6669
6989
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
6670
6990
  );
@@ -6684,7 +7004,7 @@ async function runEncodeStage(input) {
6684
7004
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
6685
7005
  fps: job.config.fps,
6686
7006
  loop,
6687
- palettePath: join12(dirname6(videoOnlyPath), "gif-palette.png"),
7007
+ palettePath: join13(dirname7(videoOnlyPath), "gif-palette.png"),
6688
7008
  preserveAlpha: needsAlpha,
6689
7009
  signal: abortSignal,
6690
7010
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -6743,9 +7063,9 @@ function sampleDirectoryBytes(dir) {
6743
7063
  continue;
6744
7064
  }
6745
7065
  for (const name of entries) {
6746
- const full = join13(current, name);
7066
+ const full = join14(current, name);
6747
7067
  try {
6748
- const st = statSync4(full);
7068
+ const st = statSync5(full);
6749
7069
  if (st.isDirectory()) {
6750
7070
  stack.push(full);
6751
7071
  } else if (st.isFile()) {
@@ -6829,8 +7149,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
6829
7149
  const ranges = [];
6830
7150
  let rangeStart = null;
6831
7151
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
6832
- const framePath = join13(framesDir, formatCaptureFrameName(frameIndex, frameExt));
6833
- 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;
6834
7154
  if (missing && rangeStart === null) {
6835
7155
  rangeStart = frameIndex;
6836
7156
  } else if (!missing && rangeStart !== null) {
@@ -6846,7 +7166,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
6846
7166
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
6847
7167
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
6848
7168
  const batches = [];
6849
- 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}`);
6850
7170
  if (stride > 1) {
6851
7171
  const spans = /* @__PURE__ */ new Map();
6852
7172
  for (const range of ranges) {
@@ -6914,7 +7234,7 @@ function isRecoverableParallelCaptureError(error) {
6914
7234
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
6915
7235
  let captured = 0;
6916
7236
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
6917
- const framePath = join13(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7237
+ const framePath = join14(framesDir, formatCaptureFrameName(frameIndex, frameExt));
6918
7238
  if (existsSync10(framePath)) captured++;
6919
7239
  }
6920
7240
  return captured;
@@ -6940,7 +7260,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
6940
7260
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
6941
7261
  });
6942
7262
  pendingTransientRetry = false;
6943
- const attemptWorkDir = join13(options.workDir, `capture-attempt-${attempt}`);
7263
+ const attemptWorkDir = join14(options.workDir, `capture-attempt-${attempt}`);
6944
7264
  const batches = missingRanges ? buildMissingFrameRetryBatches(
6945
7265
  missingRanges,
6946
7266
  currentWorkers,
@@ -7131,7 +7451,7 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
7131
7451
  var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
7132
7452
 
7133
7453
  // src/services/distributed/planFrameCost.ts
7134
- import { join as join16 } from "node:path";
7454
+ import { join as join17 } from "node:path";
7135
7455
  import {
7136
7456
  closeCaptureSession as closeCaptureSession9,
7137
7457
  createCaptureSession as createCaptureSession9,
@@ -7141,7 +7461,7 @@ import {
7141
7461
 
7142
7462
  // src/services/distributed/chunkFrames.ts
7143
7463
  import { existsSync as existsSync12, readdirSync as readdirSync4 } from "node:fs";
7144
- import { extname as extname2, join as join15 } from "node:path";
7464
+ import { extname as extname2, join as join16 } from "node:path";
7145
7465
  import {
7146
7466
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
7147
7467
  createFrameLookupTable as createFrameLookupTable2,
@@ -7151,8 +7471,8 @@ import {
7151
7471
 
7152
7472
  // src/services/distributed/shared.ts
7153
7473
  import { execFile as execFileCallback } from "node:child_process";
7154
- import { dirname as dirname8, join as join14 } from "node:path";
7155
- 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";
7156
7476
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7157
7477
  import { promisify as promisify2 } from "node:util";
7158
7478
  import {
@@ -7167,7 +7487,7 @@ function isPlanAudioArtifactPath(path) {
7167
7487
  }
7168
7488
  function resolvePlanAudioPath(planDir) {
7169
7489
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7170
- const candidate = join14(planDir, name);
7490
+ const candidate = join15(planDir, name);
7171
7491
  if (existsSync11(candidate)) return candidate;
7172
7492
  }
7173
7493
  return null;
@@ -7424,13 +7744,13 @@ function buildSyntheticRenderJob(input) {
7424
7744
  var cachedProducerVersion = null;
7425
7745
  function readProducerVersion() {
7426
7746
  if (cachedProducerVersion !== null) return cachedProducerVersion;
7427
- const startDir = dirname8(fileURLToPath3(import.meta.url));
7747
+ const startDir = dirname9(fileURLToPath3(import.meta.url));
7428
7748
  let current = startDir;
7429
7749
  for (let i = 0; i < 10; i++) {
7430
- const candidate = join14(current, "package.json");
7750
+ const candidate = join15(current, "package.json");
7431
7751
  if (existsSync11(candidate)) {
7432
7752
  try {
7433
- const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
7753
+ const pkg = JSON.parse(readFileSync7(candidate, "utf-8"));
7434
7754
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
7435
7755
  cachedProducerVersion = pkg.version;
7436
7756
  return pkg.version;
@@ -7438,7 +7758,7 @@ function readProducerVersion() {
7438
7758
  } catch {
7439
7759
  }
7440
7760
  }
7441
- const parent = dirname8(current);
7761
+ const parent = dirname9(current);
7442
7762
  if (parent === current) break;
7443
7763
  current = parent;
7444
7764
  }
@@ -7481,7 +7801,7 @@ function frameNumberFromFileName(name) {
7481
7801
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
7482
7802
  const result = [];
7483
7803
  for (const v of videos) {
7484
- const outputDir = join15(planDir, "video-frames", v.videoId);
7804
+ const outputDir = join16(planDir, "video-frames", v.videoId);
7485
7805
  if (!existsSync12(outputDir)) {
7486
7806
  throw new Error(
7487
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.`
@@ -7500,7 +7820,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
7500
7820
  if (!frameName) continue;
7501
7821
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
7502
7822
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
7503
- framePaths.set(frameIndex, join15(outputDir, frameName));
7823
+ framePaths.set(frameIndex, join16(outputDir, frameName));
7504
7824
  }
7505
7825
  result.push({
7506
7826
  videoId: v.videoId,
@@ -7590,14 +7910,14 @@ async function extractDeferredVideoFramesForChunk(input) {
7590
7910
  const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
7591
7911
  return Promise.all(
7592
7912
  deferred.map(async (video) => {
7593
- const outputDir = join15(input.outputRoot, video.videoId);
7913
+ const outputDir = join16(input.outputRoot, video.videoId);
7594
7914
  const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
7595
7915
  if (indexes.length === 0) {
7596
7916
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
7597
7917
  }
7598
7918
  const range = video.deferredRange;
7599
7919
  const remoteUrl = remoteSources[range.sourcePath];
7600
- const sourcePath = remoteUrl ?? join15(input.planDir, ...range.sourcePath.split("/"));
7920
+ const sourcePath = remoteUrl ?? join16(input.planDir, ...range.sourcePath.split("/"));
7601
7921
  if (remoteUrl === void 0 && !existsSync12(sourcePath)) {
7602
7922
  throw new RenderChunkValidationError(
7603
7923
  MISSING_PLAN_ARTIFACT,
@@ -7646,12 +7966,12 @@ async function measurePlanSecondsPerFrame(input) {
7646
7966
  planVideos,
7647
7967
  frames: sampled,
7648
7968
  fps: fpsRatio,
7649
- outputRoot: join16(workDir, "frame-cost-video-frames"),
7969
+ outputRoot: join17(workDir, "frame-cost-video-frames"),
7650
7970
  cfg: input.cfg
7651
7971
  })
7652
7972
  ];
7653
7973
  const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
7654
- const compiledDir = join16(planDir, "compiled");
7974
+ const compiledDir = join17(planDir, "compiled");
7655
7975
  const fileServer = await createFileServer({
7656
7976
  projectDir: compiledDir,
7657
7977
  compiledDir,
@@ -7663,7 +7983,7 @@ async function measurePlanSecondsPerFrame(input) {
7663
7983
  try {
7664
7984
  session = await createCaptureSession9(
7665
7985
  fileServer.url,
7666
- join16(workDir, "frame-cost"),
7986
+ join17(workDir, "frame-cost"),
7667
7987
  buildChunkCaptureOptions({
7668
7988
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
7669
7989
  encoder: input,
@@ -7760,8 +8080,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
7760
8080
  }
7761
8081
 
7762
8082
  // src/services/render/stages/freezePlan.ts
7763
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
7764
- 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";
7765
8085
 
7766
8086
  // src/services/distributed/planProtocol.ts
7767
8087
  var PLAN_SCHEMA_VERSION = 1;
@@ -7859,12 +8179,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
7859
8179
  }
7860
8180
 
7861
8181
  // src/services/render/stages/planHash.ts
7862
- import { createHash as createHash4 } from "node:crypto";
7863
- 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";
7864
8184
  var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
7865
8185
  var FIELD_DELIMITER = Buffer.from([0]);
7866
8186
  function computePlanHash(input) {
7867
- const hash = createHash4("sha256");
8187
+ const hash = createHash5("sha256");
7868
8188
  hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
7869
8189
  hash.update(input.compositionHtml);
7870
8190
  hash.update(FIELD_DELIMITER);
@@ -7911,27 +8231,27 @@ function canonicalJsonStringify(value) {
7911
8231
  throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
7912
8232
  }
7913
8233
  function sha256Hex(bytes) {
7914
- const h = createHash4("sha256");
8234
+ const h = createHash5("sha256");
7915
8235
  h.update(bytes);
7916
8236
  return h.digest("hex");
7917
8237
  }
7918
8238
  function sha256File(path) {
7919
- const hash = createHash4("sha256");
8239
+ const hash = createHash5("sha256");
7920
8240
  const buffer = Buffer.allocUnsafe(1024 * 1024);
7921
- const fd = openSync2(path, "r");
8241
+ const fd = openSync3(path, "r");
7922
8242
  try {
7923
8243
  let bytesRead = 0;
7924
8244
  do {
7925
- bytesRead = readSync(fd, buffer, 0, buffer.byteLength, null);
8245
+ bytesRead = readSync2(fd, buffer, 0, buffer.byteLength, null);
7926
8246
  if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
7927
8247
  } while (bytesRead > 0);
7928
8248
  } finally {
7929
- closeSync2(fd);
8249
+ closeSync3(fd);
7930
8250
  }
7931
8251
  return hash.digest("hex");
7932
8252
  }
7933
8253
  async function sha256FileAsync(path) {
7934
- const hash = createHash4("sha256");
8254
+ const hash = createHash5("sha256");
7935
8255
  for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
7936
8256
  hash.update(chunk);
7937
8257
  }
@@ -7975,11 +8295,11 @@ function stripUndefined(value) {
7975
8295
  }
7976
8296
  function listPlanFiles(planDir) {
7977
8297
  const results = [];
7978
- const rootResolved = resolve7(planDir);
8298
+ const rootResolved = resolve8(planDir);
7979
8299
  function walk(dir) {
7980
8300
  const entries = readdirSync5(dir, { withFileTypes: true });
7981
8301
  for (const entry of entries) {
7982
- const full = join17(dir, entry.name);
8302
+ const full = join18(dir, entry.name);
7983
8303
  if (entry.isDirectory()) {
7984
8304
  walk(full);
7985
8305
  } else if (entry.isFile()) {
@@ -8000,7 +8320,7 @@ function collectPlanAssetShas(planDir, digests) {
8000
8320
  const assets = [];
8001
8321
  for (const file of files) {
8002
8322
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
8003
- compositionHtml = readFileSync7(file.absolutePath);
8323
+ compositionHtml = readFileSync8(file.absolutePath);
8004
8324
  continue;
8005
8325
  }
8006
8326
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -8020,23 +8340,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
8020
8340
  function writePlanRemoteFiles(planDir, files) {
8021
8341
  if (files.length === 0) return;
8022
8342
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
8023
- 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");
8024
8344
  }
8025
8345
  function readPlanRemoteFiles(planDir) {
8026
- const path = join17(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8027
- 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")) : [];
8028
8348
  }
8029
8349
  function recomputePlanHashFromPlanDir(planDir, digests) {
8030
- const planJsonPath = join17(planDir, "plan.json");
8031
- const encoderJsonPath = join17(planDir, "meta", "encoder.json");
8350
+ const planJsonPath = join18(planDir, "plan.json");
8351
+ const encoderJsonPath = join18(planDir, "meta", "encoder.json");
8032
8352
  if (!existsSync13(planJsonPath)) {
8033
8353
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8034
8354
  }
8035
8355
  if (!existsSync13(encoderJsonPath)) {
8036
8356
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8037
8357
  }
8038
- const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
8039
- const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
8358
+ const planJson = JSON.parse(readFileSync8(planJsonPath, "utf-8"));
8359
+ const encoderConfigCanonicalJson = readFileSync8(encoderJsonPath, "utf-8");
8040
8360
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8041
8361
  return computePlanHash({
8042
8362
  compositionHtml,
@@ -8065,18 +8385,18 @@ async function freezePlan(input) {
8065
8385
  if (!existsSync13(planDir)) {
8066
8386
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8067
8387
  }
8068
- const metaDir = join17(planDir, "meta");
8069
- if (!existsSync13(metaDir)) mkdirSync6(metaDir, { recursive: true });
8070
- writeFileSync4(
8071
- 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"),
8072
8392
  `${JSON.stringify(composition, null, 2)}
8073
8393
  `,
8074
8394
  "utf-8"
8075
8395
  );
8076
8396
  const encoderForCanonical = stripUndefined(encoder);
8077
8397
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
8078
- writeFileSync4(join17(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8079
- 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)}
8080
8400
  `, "utf-8");
8081
8401
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8082
8402
  const planHash = computePlanHash({
@@ -8100,8 +8420,8 @@ async function freezePlan(input) {
8100
8420
  duration: durationSeconds,
8101
8421
  hasAudio
8102
8422
  };
8103
- const planJsonPath = join17(planDir, "plan.json");
8104
- writeFileSync4(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8423
+ const planJsonPath = join18(planDir, "plan.json");
8424
+ writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8105
8425
  `, "utf-8");
8106
8426
  return { planJsonPath, planHash };
8107
8427
  }
@@ -8142,16 +8462,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8142
8462
  }
8143
8463
 
8144
8464
  // src/services/distributed/chunkAudio.ts
8145
- import { closeSync as closeSync3, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync8 } from "node:fs";
8146
- import { writeFileSync as writeFileSync5, writeSync } from "node:fs";
8147
- import { join as join19 } from "node:path";
8148
- 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";
8149
8469
 
8150
8470
  // src/services/distributed/mp4Stitch.ts
8151
- 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";
8152
8472
  import { open } from "node:fs/promises";
8153
- import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
8154
- 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";
8155
8475
  import { finished, pipeline } from "node:stream/promises";
8156
8476
  function boxAt(src, off, at, limit) {
8157
8477
  let size = src.readUInt32BE(off);
@@ -8164,16 +8484,16 @@ function boxAt(src, off, at, limit) {
8164
8484
  return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
8165
8485
  }
8166
8486
  function childBoxes(buf, parent) {
8167
- const boxes = [];
8168
- for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
8169
- 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));
8170
8490
  }
8171
- return boxes;
8491
+ return boxes2;
8172
8492
  }
8173
8493
  function findChild(buf, parent, type) {
8174
8494
  return childBoxes(buf, parent).find((box2) => box2.type === type);
8175
8495
  }
8176
- function child(buf, parent, type) {
8496
+ function child2(buf, parent, type) {
8177
8497
  const found = findChild(buf, parent, type);
8178
8498
  if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
8179
8499
  return found;
@@ -8225,7 +8545,7 @@ function rows(buf, table, columns, countAt = 4) {
8225
8545
  function readMediaTime(moov, trak) {
8226
8546
  const edts = findChild(moov, trak, "edts");
8227
8547
  if (!edts) return 0;
8228
- const elst = child(moov, edts, "elst");
8548
+ const elst = child2(moov, edts, "elst");
8229
8549
  const wide = moov[elst.bodyStart] === 1;
8230
8550
  const entries = moov.readUInt32BE(elst.bodyStart + 4);
8231
8551
  for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
@@ -8240,7 +8560,7 @@ function readSizes(moov, stsz) {
8240
8560
  if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
8241
8561
  return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
8242
8562
  }
8243
- function readSync2(moov, stbl, sampleCount) {
8563
+ function readSync3(moov, stbl, sampleCount) {
8244
8564
  const stss = findChild(moov, stbl, "stss");
8245
8565
  if (!stss) return null;
8246
8566
  const sync = Array(sampleCount).fill(false);
@@ -8250,10 +8570,10 @@ function readSync2(moov, stbl, sampleCount) {
8250
8570
  function readChunkOffsets(moov, stbl) {
8251
8571
  const stco = findChild(moov, stbl, "stco");
8252
8572
  if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
8253
- 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);
8254
8574
  }
8255
8575
  function readSamplesPerChunk(moov, stbl, chunkCount) {
8256
- const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
8576
+ const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
8257
8577
  return stsc.flatMap(([firstChunk, perChunk, description], i) => {
8258
8578
  if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
8259
8579
  const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
@@ -8261,28 +8581,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
8261
8581
  });
8262
8582
  }
8263
8583
  function readTrack(moov, trak) {
8264
- const mdia = child(moov, trak, "mdia");
8265
- const hdlr = child(moov, mdia, "hdlr");
8266
- 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");
8267
8587
  if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
8268
- const stts = rows(moov, child(moov, stbl, "stts"), 2);
8588
+ const stts = rows(moov, child2(moov, stbl, "stts"), 2);
8269
8589
  const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
8270
- const sizes = readSizes(moov, child(moov, stbl, "stsz"));
8590
+ const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
8271
8591
  const chunkOffsets = readChunkOffsets(moov, stbl);
8272
8592
  const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
8273
8593
  if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
8274
8594
  throw new Error("[mp4Stitch] sample tables disagree on the sample count");
8275
8595
  }
8276
- const stsd = child(moov, stbl, "stsd");
8596
+ const stsd = child2(moov, stbl, "stsd");
8277
8597
  return {
8278
8598
  trak,
8279
8599
  handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
8280
- timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
8600
+ timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
8281
8601
  mediaTime: readMediaTime(moov, trak),
8282
8602
  stsd: moov.subarray(stsd.start, stsd.end),
8283
8603
  durations,
8284
8604
  sizes,
8285
- sync: readSync2(moov, stbl, sizes.length),
8605
+ sync: readSync3(moov, stbl, sizes.length),
8286
8606
  samplesPerChunk,
8287
8607
  chunkOffsets
8288
8608
  };
@@ -8423,9 +8743,9 @@ function buildStitchedHeader(layouts, audio) {
8423
8743
  }
8424
8744
  const template = layouts[0].moov;
8425
8745
  const root = boxAt(template, 0, 0, template.length);
8426
- const mvhd = child(template, root, "mvhd");
8746
+ const mvhd = child2(template, root, "mvhd");
8427
8747
  const movieTimescale = timescaleOf(template, mvhd);
8428
- 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")));
8429
8749
  const audioTrackId = Math.max(...chunkTrackIds) + 1;
8430
8750
  const buildMoov = (dataStart2) => {
8431
8751
  const traks = plan2.map((track, t) => {
@@ -8515,18 +8835,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
8515
8835
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
8516
8836
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
8517
8837
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
8518
- mkdirSync7(dirname9(outputPath), { recursive: true });
8838
+ mkdirSync8(dirname10(outputPath), { recursive: true });
8519
8839
  const workDir = mkdtempSync2(`${outputPath}.stitch-`);
8520
- const out = createWriteStream(join18(workDir, "out.mp4"));
8840
+ const out = createWriteStream(join19(workDir, "out.mp4"));
8521
8841
  try {
8522
8842
  out.write(header);
8523
8843
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
8524
8844
  out.end();
8525
8845
  await finished(out);
8526
- renameWithRetry5(join18(workDir, "out.mp4"), outputPath);
8846
+ renameWithRetry6(join19(workDir, "out.mp4"), outputPath);
8527
8847
  } finally {
8528
8848
  out.destroy();
8529
- rmSync7(workDir, { recursive: true, force: true });
8849
+ rmSync8(workDir, { recursive: true, force: true });
8530
8850
  }
8531
8851
  }
8532
8852
 
@@ -8615,9 +8935,9 @@ async function writeChunkAudioSlices(input) {
8615
8935
  offset += track.sizes[offsets.length - 1];
8616
8936
  }
8617
8937
  });
8618
- const bytes = readFileSync8(input.audioPath);
8938
+ const bytes = readFileSync9(input.audioPath);
8619
8939
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
8620
- mkdirSync8(join19(input.planDir, SLICE_DIR), { recursive: true });
8940
+ mkdirSync9(join20(input.planDir, SLICE_DIR), { recursive: true });
8621
8941
  input.chunks.forEach((_, index) => {
8622
8942
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
8623
8943
  if (!cut) return;
@@ -8625,18 +8945,18 @@ async function writeChunkAudioSlices(input) {
8625
8945
  const start = offsets[cut.firstPacket];
8626
8946
  layout.partStarts[index] = start;
8627
8947
  const end = offsets[last] + track.sizes[last];
8628
- writeFileSync5(join19(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
8948
+ writeFileSync6(join20(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
8629
8949
  });
8630
8950
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
8631
- writeFileSync5(join19(input.planDir, SLICE_FRAME), Buffer.concat(frame));
8632
- 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));
8633
8953
  }
8634
8954
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
8635
- const layoutPath = join19(planDir, SLICE_LAYOUT);
8955
+ const layoutPath = join20(planDir, SLICE_LAYOUT);
8636
8956
  if (!existsSync14(layoutPath)) return resolvePlanAudioPath(planDir);
8637
- const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
8638
- const frame = readFileSync8(join19(planDir, SLICE_FRAME));
8639
- 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");
8640
8960
  try {
8641
8961
  writeSync(fd, frame, 0, layout.payloadStart, 0);
8642
8962
  writeSync(
@@ -8648,12 +8968,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
8648
8968
  );
8649
8969
  const start = layout.partStarts[chunkIndex];
8650
8970
  if (start !== void 0) {
8651
- const part = readFileSync8(join19(planDir, chunkAudioSlicePath(chunkIndex)));
8971
+ const part = readFileSync9(join20(planDir, chunkAudioSlicePath(chunkIndex)));
8652
8972
  writeSync(fd, part, 0, part.length, start);
8653
8973
  }
8654
8974
  ftruncateSync(fd, layout.sizeBytes);
8655
8975
  } finally {
8656
- closeSync3(fd);
8976
+ closeSync4(fd);
8657
8977
  }
8658
8978
  return scratchPath;
8659
8979
  }
@@ -8673,7 +8993,7 @@ async function muxChunkWithAudio(input) {
8673
8993
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
8674
8994
  appendRenderProvenanceArgs(args, input.outputPath);
8675
8995
  args.push("-y", input.outputPath);
8676
- const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
8996
+ const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
8677
8997
  if (!result.success) {
8678
8998
  throw new Error(
8679
8999
  `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
@@ -8702,9 +9022,9 @@ async function muxChunkWithAudio(input) {
8702
9022
  }
8703
9023
 
8704
9024
  // src/services/distributed/planSize.ts
8705
- import { createHash as createHash5 } from "node:crypto";
9025
+ import { createHash as createHash6 } from "node:crypto";
8706
9026
  import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
8707
- 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";
8708
9028
  var MAX_TOP_COMPONENTS = 10;
8709
9029
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
8710
9030
  ".aac",
@@ -8732,7 +9052,7 @@ var PLAN_ROOT_CATEGORY = {
8732
9052
  "plan.json": "metadata"
8733
9053
  };
8734
9054
  function hashComponent(value) {
8735
- return createHash5("sha256").update(value).digest("hex").slice(0, 12);
9055
+ return createHash6("sha256").update(value).digest("hex").slice(0, 12);
8736
9056
  }
8737
9057
  function classifyFile(segments, rootKind) {
8738
9058
  const rootName = segments[0] ?? "";
@@ -8816,7 +9136,7 @@ function walkRegularFiles(dir, visit) {
8816
9136
  return;
8817
9137
  }
8818
9138
  for (const entry of entries) {
8819
- const full = join20(dir, entry.name);
9139
+ const full = join21(dir, entry.name);
8820
9140
  if (entry.isDirectory()) {
8821
9141
  walkRegularFiles(full, visit);
8822
9142
  } else if (entry.isFile()) {
@@ -9032,13 +9352,13 @@ function assertPositiveInteger(name, value) {
9032
9352
  }
9033
9353
  var FREEZE_OWNED_PLAN_FILES = [
9034
9354
  "plan.json",
9035
- join21("meta", "composition.json"),
9036
- join21("meta", "encoder.json"),
9037
- join21("meta", "chunks.json")
9355
+ join22("meta", "composition.json"),
9356
+ join22("meta", "encoder.json"),
9357
+ join22("meta", "chunks.json")
9038
9358
  ];
9039
9359
  function removeFreezeOwnedPlanFiles(planDir) {
9040
9360
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
9041
- rmSync8(join21(planDir, relativePath), { force: true });
9361
+ rmSync9(join22(planDir, relativePath), { force: true });
9042
9362
  }
9043
9363
  }
9044
9364
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -9183,23 +9503,23 @@ function createVideoSourceShipper(input) {
9183
9503
  const known = shipped.get(srcPath);
9184
9504
  if (known) return known;
9185
9505
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
9186
- const current = stagedRel === null ? srcPath : join21(input.planDir, "video-frames", stagedRel);
9506
+ const current = stagedRel === null ? srcPath : join22(input.planDir, "video-frames", stagedRel);
9187
9507
  let result = null;
9188
9508
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9189
9509
  const rel = pathInside(root, srcPath);
9190
- 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))) {
9191
9511
  result = ["compiled", ...rel.split(sep2)].join("/");
9192
9512
  break;
9193
9513
  }
9194
9514
  }
9195
9515
  if (result === null) {
9196
9516
  result = `video-sources/${shipped.size}${extname4(current)}`;
9197
- const destination = join21(input.planDir, ...result.split("/"));
9198
- 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 });
9199
9519
  try {
9200
9520
  linkSync(current, destination);
9201
9521
  } catch {
9202
- copyFileSync5(current, destination);
9522
+ copyFileSync6(current, destination);
9203
9523
  }
9204
9524
  }
9205
9525
  shipped.set(srcPath, result);
@@ -9215,7 +9535,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9215
9535
  useGpu: false,
9216
9536
  browserGpuMode: "software"
9217
9537
  });
9218
- if (!existsSync15(planDir)) mkdirSync9(planDir, { recursive: true });
9538
+ if (!existsSync15(planDir)) mkdirSync10(planDir, { recursive: true });
9219
9539
  const log = config.logger ?? defaultLogger;
9220
9540
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
9221
9541
  const abortSignal = config.abortSignal;
@@ -9244,15 +9564,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9244
9564
  variables: config.variables
9245
9565
  });
9246
9566
  const entryFile = config.entryFile ?? "index.html";
9247
- const htmlPath = join21(projectDir, entryFile);
9567
+ const htmlPath = join22(projectDir, entryFile);
9248
9568
  if (!existsSync15(htmlPath)) {
9249
9569
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
9250
9570
  }
9251
- const workDir = join21(planDir, ".plan-work");
9252
- rmSync8(workDir, { recursive: true, force: true });
9253
- mkdirSync9(workDir, { recursive: true });
9254
- const compiledDir = join21(workDir, "compiled");
9255
- 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 });
9256
9576
  let stageStarted = Date.now();
9257
9577
  cpSync2(projectDir, compiledDir, {
9258
9578
  recursive: true,
@@ -9265,10 +9585,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9265
9585
  }
9266
9586
  });
9267
9587
  logPlanStage(log, "copy", stageStarted);
9268
- const finalCompiledDir = join21(planDir, "compiled");
9588
+ const finalCompiledDir = join22(planDir, "compiled");
9269
9589
  const needsAlpha = outputNeedsAlpha(config.format);
9270
9590
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
9271
- const remoteLocalPath = (rel) => join21(projectDir, ...rel.split("/"));
9591
+ const remoteLocalPath = (rel) => join22(projectDir, ...rel.split("/"));
9272
9592
  for (const [, file] of remoteFiles) {
9273
9593
  declareContentAddressedUrl(file.url, file.sha256);
9274
9594
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -9454,18 +9774,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9454
9774
  let stageStarted = Date.now();
9455
9775
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
9456
9776
  logPlanStage(log, "extract", stageStarted);
9457
- const stagedVideoFrames = join21(compiledDir, "__hyperframes_video_frames");
9458
- const videoFramesDst = join21(planDir, "video-frames");
9459
- if (existsSync15(videoFramesDst)) rmSync8(videoFramesDst, { recursive: true, force: true });
9460
- const videoSourcesDir = join21(planDir, "video-sources");
9461
- 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 });
9462
9782
  if (existsSync15(stagedVideoFrames)) {
9463
- renameWithRetry6(stagedVideoFrames, videoFramesDst);
9783
+ renameWithRetry7(stagedVideoFrames, videoFramesDst);
9464
9784
  } else {
9465
- mkdirSync9(videoFramesDst, { recursive: true });
9785
+ mkdirSync10(videoFramesDst, { recursive: true });
9466
9786
  }
9467
- if (existsSync15(finalCompiledDir)) rmSync8(finalCompiledDir, { recursive: true, force: true });
9468
- renameWithRetry6(compiledDir, finalCompiledDir);
9787
+ if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
9788
+ renameWithRetry7(compiledDir, finalCompiledDir);
9469
9789
  const shipVideoSource = createVideoSourceShipper({
9470
9790
  planDir,
9471
9791
  projectDir,
@@ -9494,7 +9814,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9494
9814
  };
9495
9815
  })
9496
9816
  });
9497
- mkdirSync9(join21(planDir, "meta"), { recursive: true });
9817
+ mkdirSync10(join22(planDir, "meta"), { recursive: true });
9498
9818
  writePlanRemoteFiles(
9499
9819
  planDir,
9500
9820
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -9503,8 +9823,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9503
9823
  sizeBytes
9504
9824
  }))
9505
9825
  );
9506
- writeFileSync6(
9507
- join21(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
9826
+ writeFileSync7(
9827
+ join22(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
9508
9828
  JSON.stringify(planVideosJson, null, 2),
9509
9829
  "utf-8"
9510
9830
  );
@@ -9583,9 +9903,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9583
9903
  if (audioResult?.audioError) {
9584
9904
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
9585
9905
  }
9586
- const planAudioPath = join21(planDir, PLAN_AUDIO_RELATIVE_PATH);
9906
+ const planAudioPath = join22(planDir, PLAN_AUDIO_RELATIVE_PATH);
9587
9907
  if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
9588
- renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
9908
+ renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
9589
9909
  }
9590
9910
  if (chunksCarryAudio && existsSync15(planAudioPath)) {
9591
9911
  if (options.sliceChunkAudio) {
@@ -9632,7 +9952,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9632
9952
  format: config.format
9633
9953
  };
9634
9954
  try {
9635
- rmSync8(workDir, { recursive: true, force: true });
9955
+ rmSync9(workDir, { recursive: true, force: true });
9636
9956
  } catch (err) {
9637
9957
  log.warn("[plan] failed to remove temp work dir", {
9638
9958
  workDir,
@@ -9699,10 +10019,10 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
9699
10019
  applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
9700
10020
  }
9701
10021
  if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
9702
- renameWithRetry6(mixed.audioOutputPath, outputPath);
10022
+ renameWithRetry7(mixed.audioOutputPath, outputPath);
9703
10023
  return { hasAudio: true };
9704
10024
  } finally {
9705
- rmSync8(c.workDir, { recursive: true, force: true });
10025
+ rmSync9(c.workDir, { recursive: true, force: true });
9706
10026
  }
9707
10027
  }
9708
10028
  async function plan(projectDir, config, planDir, options = {}) {
@@ -9724,35 +10044,35 @@ async function plan(projectDir, config, planDir, options = {}) {
9724
10044
 
9725
10045
  // src/services/distributed/planV2.ts
9726
10046
  import {
9727
- copyFileSync as copyFileSync7,
10047
+ copyFileSync as copyFileSync8,
9728
10048
  existsSync as existsSync17,
9729
10049
  lstatSync as lstatSync2,
9730
- mkdirSync as mkdirSync11,
10050
+ mkdirSync as mkdirSync12,
9731
10051
  mkdtempSync as mkdtempSync4,
9732
- readFileSync as readFileSync9,
10052
+ readFileSync as readFileSync10,
9733
10053
  readdirSync as readdirSync7,
9734
- rmSync as rmSync10,
9735
- statSync as statSync6,
9736
- writeFileSync as writeFileSync8
10054
+ rmSync as rmSync11,
10055
+ statSync as statSync7,
10056
+ writeFileSync as writeFileSync9
9737
10057
  } from "node:fs";
9738
10058
  import { tmpdir } from "node:os";
9739
- 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";
9740
10060
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
9741
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
10061
+ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
9742
10062
 
9743
10063
  // src/services/distributed/planV2Publisher.ts
9744
10064
  import {
9745
- copyFileSync as copyFileSync6,
10065
+ copyFileSync as copyFileSync7,
9746
10066
  existsSync as existsSync16,
9747
10067
  linkSync as linkSync2,
9748
- mkdirSync as mkdirSync10,
10068
+ mkdirSync as mkdirSync11,
9749
10069
  mkdtempSync as mkdtempSync3,
9750
- rmSync as rmSync9,
9751
- statSync as statSync5,
9752
- writeFileSync as writeFileSync7
10070
+ rmSync as rmSync10,
10071
+ statSync as statSync6,
10072
+ writeFileSync as writeFileSync8
9753
10073
  } from "node:fs";
9754
- import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
9755
- 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";
9756
10076
 
9757
10077
  // src/services/distributed/planV2Errors.ts
9758
10078
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -9767,7 +10087,7 @@ var PlanV2IntegrityError = class extends Error {
9767
10087
  };
9768
10088
 
9769
10089
  // src/services/distributed/planV2Layout.ts
9770
- import { join as join22 } from "node:path";
10090
+ import { join as join23 } from "node:path";
9771
10091
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
9772
10092
  function assertPlanV2Sha256(value, field) {
9773
10093
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -9777,7 +10097,7 @@ function assertPlanV2Sha256(value, field) {
9777
10097
  }
9778
10098
  function planV2BlobPath(root, sha256) {
9779
10099
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
9780
- return join22(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10100
+ return join23(root, "artifacts", "sha256", digest.slice(0, 2), digest);
9781
10101
  }
9782
10102
 
9783
10103
  // src/services/distributed/planV2Publisher.ts
@@ -9817,12 +10137,12 @@ var LocalPlanV2ArtifactPublisher = class {
9817
10137
  }
9818
10138
  this.destinationDir = destinationDir;
9819
10139
  this.#linkFile = options.linkFile ?? linkSync2;
9820
- mkdirSync10(dirname10(destinationDir), { recursive: true });
9821
- 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-"));
9822
10142
  }
9823
10143
  async putBlob(blob) {
9824
10144
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
9825
- const sourceSize = statSync5(blob.sourcePath).size;
10145
+ const sourceSize = statSync6(blob.sourcePath).size;
9826
10146
  if (sourceSize !== blob.sizeBytes) {
9827
10147
  throw new PlanV2IntegrityError(
9828
10148
  `published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
@@ -9830,19 +10150,19 @@ var LocalPlanV2ArtifactPublisher = class {
9830
10150
  }
9831
10151
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
9832
10152
  if (existsSync16(destinationPath)) return;
9833
- mkdirSync10(dirname10(destinationPath), { recursive: true });
9834
- const stagingDir = mkdtempSync3(join23(dirname10(destinationPath), ".plan-v2-blob-"));
9835
- 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");
9836
10156
  try {
9837
10157
  try {
9838
10158
  this.#linkFile(blob.sourcePath, temporaryPath);
9839
10159
  } catch (error) {
9840
10160
  if (!canFallbackToCopy(error)) throw error;
9841
- copyFileSync6(blob.sourcePath, temporaryPath);
10161
+ copyFileSync7(blob.sourcePath, temporaryPath);
9842
10162
  }
9843
- renameWithRetry7(temporaryPath, destinationPath);
10163
+ renameWithRetry8(temporaryPath, destinationPath);
9844
10164
  } finally {
9845
- rmSync9(stagingDir, { recursive: true, force: true });
10165
+ rmSync10(stagingDir, { recursive: true, force: true });
9846
10166
  }
9847
10167
  }
9848
10168
  async commitManifest(manifestBytes) {
@@ -9853,13 +10173,13 @@ var LocalPlanV2ArtifactPublisher = class {
9853
10173
  );
9854
10174
  }
9855
10175
  }
9856
- writeFileSync7(join23(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
9857
- renameWithRetry7(this.temporaryDir, this.destinationDir);
10176
+ writeFileSync8(join24(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10177
+ renameWithRetry8(this.temporaryDir, this.destinationDir);
9858
10178
  this.#committed = true;
9859
10179
  }
9860
10180
  async abort() {
9861
10181
  if (!this.#committed) {
9862
- rmSync9(this.temporaryDir, { recursive: true, force: true });
10182
+ rmSync10(this.temporaryDir, { recursive: true, force: true });
9863
10183
  }
9864
10184
  }
9865
10185
  };
@@ -9878,7 +10198,7 @@ function isNonNegativeIntegerArray(value) {
9878
10198
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
9879
10199
  }
9880
10200
  function readJsonFile(path, label) {
9881
- const contents = readFileSync9(path, "utf-8");
10201
+ const contents = readFileSync10(path, "utf-8");
9882
10202
  try {
9883
10203
  const parsed = JSON.parse(contents);
9884
10204
  return parsed;
@@ -9895,10 +10215,10 @@ function assertSafeRelativePath(path) {
9895
10215
  }
9896
10216
  function listFiles(root) {
9897
10217
  const files = [];
9898
- const rootResolved = resolve8(root);
10218
+ const rootResolved = resolve9(root);
9899
10219
  function walk(dir) {
9900
10220
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
9901
- const absolutePath = join24(dir, entry.name);
10221
+ const absolutePath = join25(dir, entry.name);
9902
10222
  if (entry.isDirectory()) {
9903
10223
  walk(absolutePath);
9904
10224
  } else if (entry.isFile()) {
@@ -9925,13 +10245,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
9925
10245
  }
9926
10246
  }
9927
10247
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
9928
- const videoRoot = join24(executionPlanDir, "video-frames");
10248
+ const videoRoot = join25(executionPlanDir, "video-frames");
9929
10249
  if (!existsSync17(videoRoot)) return;
9930
10250
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
9931
10251
  if (!videoEntry.isDirectory()) continue;
9932
- const videoDir = join24(videoRoot, videoEntry.name);
10252
+ const videoDir = join25(videoRoot, videoEntry.name);
9933
10253
  if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
9934
- const sentinelPath = join24(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10254
+ const sentinelPath = join25(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
9935
10255
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
9936
10256
  }
9937
10257
  }
@@ -9941,8 +10261,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
9941
10261
  return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
9942
10262
  }
9943
10263
  function resolveExtractedVideoOutputDir(planDir, videoId) {
9944
- const videoRoot = resolve8(planDir, "video-frames");
9945
- const outputDir = resolve8(videoRoot, videoId);
10264
+ const videoRoot = resolve9(planDir, "video-frames");
10265
+ const outputDir = resolve9(videoRoot, videoId);
9946
10266
  if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep3}`)) {
9947
10267
  throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
9948
10268
  }
@@ -9978,7 +10298,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
9978
10298
  if (video.deferredRange) {
9979
10299
  const framePaths2 = /* @__PURE__ */ new Map();
9980
10300
  for (let index = 0; index < video.totalFrames; index++) {
9981
- framePaths2.set(index, join24(outputDir, String(index)));
10301
+ framePaths2.set(index, join25(outputDir, String(index)));
9982
10302
  }
9983
10303
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
9984
10304
  }
@@ -9986,7 +10306,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
9986
10306
  const framePaths = /* @__PURE__ */ new Map();
9987
10307
  for (const frameName of frameNames) {
9988
10308
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
9989
- assertValidExtractionCacheCompleteSentinel(join24(outputDir, frameName));
10309
+ assertValidExtractionCacheCompleteSentinel(join25(outputDir, frameName));
9990
10310
  continue;
9991
10311
  }
9992
10312
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -10005,7 +10325,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10005
10325
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
10006
10326
  );
10007
10327
  }
10008
- framePaths.set(frameIndex, join24(outputDir, frameName));
10328
+ framePaths.set(frameIndex, join25(outputDir, frameName));
10009
10329
  }
10010
10330
  return {
10011
10331
  ...video,
@@ -10026,11 +10346,11 @@ function parsePlanVideosJson2(value) {
10026
10346
  }
10027
10347
  }
10028
10348
  function materializeExtractedVideoDirectories(planDir) {
10029
- const videosPath = join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10349
+ const videosPath = join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10030
10350
  if (!existsSync17(videosPath)) return;
10031
10351
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10032
10352
  for (const video of videos.extracted) {
10033
- mkdirSync11(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
10353
+ mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
10034
10354
  }
10035
10355
  }
10036
10356
  function parseChunkSlices(value) {
@@ -10060,13 +10380,13 @@ function parseChunkSlices(value) {
10060
10380
  });
10061
10381
  }
10062
10382
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10063
- const videoRoot = join24(executionPlanDir, "video-frames");
10383
+ const videoRoot = join25(executionPlanDir, "video-frames");
10064
10384
  const hasExtractedFrames = existsSync17(videoRoot) && listFiles(videoRoot).length > 0;
10065
- const videosPath = join24(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10385
+ const videosPath = join25(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10066
10386
  if (!existsSync17(videosPath)) {
10067
10387
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10068
10388
  }
10069
- const chunksPath = join24(executionPlanDir, "meta", "chunks.json");
10389
+ const chunksPath = join25(executionPlanDir, "meta", "chunks.json");
10070
10390
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
10071
10391
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
10072
10392
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -10103,7 +10423,7 @@ function collectChunkVideoDependencies(input) {
10103
10423
  for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
10104
10424
  const globalTime = frame * input.fpsDen / input.fpsNum;
10105
10425
  for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
10106
- 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("/");
10107
10427
  assertSafeRelativePath(path);
10108
10428
  const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
10109
10429
  indexes.add(chunk.index);
@@ -10121,21 +10441,21 @@ function computeManifestHash(manifest) {
10121
10441
  }
10122
10442
  function writeBlob(sourcePath, destinationPath) {
10123
10443
  if (existsSync17(destinationPath)) return;
10124
- mkdirSync11(dirname11(destinationPath), { recursive: true });
10125
- const temporaryDir = mkdtempSync4(join24(dirname11(destinationPath), ".plan-v2-blob-"));
10126
- 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");
10127
10447
  try {
10128
- copyFileSync7(sourcePath, temporaryPath);
10129
- renameWithRetry8(temporaryPath, destinationPath);
10448
+ copyFileSync8(sourcePath, temporaryPath);
10449
+ renameWithRetry9(temporaryPath, destinationPath);
10130
10450
  } finally {
10131
- rmSync10(temporaryDir, { recursive: true, force: true });
10451
+ rmSync11(temporaryDir, { recursive: true, force: true });
10132
10452
  }
10133
10453
  }
10134
10454
  function publishBlobFor(absolutePath, digests) {
10135
10455
  return {
10136
10456
  sourcePath: absolutePath,
10137
10457
  sha256: planFileSha256(absolutePath, digests),
10138
- sizeBytes: statSync6(absolutePath).size
10458
+ sizeBytes: statSync7(absolutePath).size
10139
10459
  };
10140
10460
  }
10141
10461
  async function putBlobs(publisher, blobs) {
@@ -10159,7 +10479,7 @@ async function putTasks(tasks) {
10159
10479
  if (failure) throw failure.reason;
10160
10480
  }
10161
10481
  function buildPlanV2Publication(executionPlanDir, digests) {
10162
- const executionPlanPath = join24(executionPlanDir, "plan.json");
10482
+ const executionPlanPath = join25(executionPlanDir, "plan.json");
10163
10483
  if (!existsSync17(executionPlanPath)) {
10164
10484
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10165
10485
  }
@@ -10187,7 +10507,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10187
10507
  }
10188
10508
  validateExtractionCacheCompleteSentinels(executionPlanDir);
10189
10509
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
10190
- const encoder = readJsonFile(join24(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10510
+ const encoder = readJsonFile(join25(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10191
10511
  const files = listFiles(executionPlanDir);
10192
10512
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
10193
10513
  for (const file of files) {
@@ -10247,21 +10567,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
10247
10567
  if (publication.storedBlobs.length > 0) {
10248
10568
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
10249
10569
  }
10250
- mkdirSync11(dirname11(planV2Dir), { recursive: true });
10251
- 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-"));
10252
10572
  try {
10253
10573
  for (const blob of publication.blobs) {
10254
10574
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
10255
10575
  }
10256
- writeFileSync8(
10257
- join24(tempDir, "plan.json"),
10576
+ writeFileSync9(
10577
+ join25(tempDir, "plan.json"),
10258
10578
  canonicalJsonStringify(publication.manifest),
10259
10579
  "utf-8"
10260
10580
  );
10261
- renameWithRetry8(tempDir, planV2Dir);
10581
+ renameWithRetry9(tempDir, planV2Dir);
10262
10582
  return resultFromManifest(planV2Dir, publication.manifest);
10263
10583
  } catch (error) {
10264
- rmSync10(tempDir, { recursive: true, force: true });
10584
+ rmSync11(tempDir, { recursive: true, force: true });
10265
10585
  throw error;
10266
10586
  }
10267
10587
  }
@@ -10301,7 +10621,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
10301
10621
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
10302
10622
  }
10303
10623
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
10304
- const stagingRoot = mkdtempSync4(join24(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
10624
+ const stagingRoot = mkdtempSync4(join25(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
10305
10625
  const digests = /* @__PURE__ */ new Map();
10306
10626
  try {
10307
10627
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -10312,7 +10632,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
10312
10632
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
10313
10633
  whileAudioMixes: () => putBlobs(
10314
10634
  publisher,
10315
- listFiles(join24(stagingRoot, "compiled")).map(
10635
+ listFiles(join25(stagingRoot, "compiled")).map(
10316
10636
  (file) => publishBlobFor(file.absolutePath, digests)
10317
10637
  )
10318
10638
  ),
@@ -10328,7 +10648,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
10328
10648
  }
10329
10649
  throw error;
10330
10650
  } finally {
10331
- rmSync10(stagingRoot, { recursive: true, force: true });
10651
+ rmSync11(stagingRoot, { recursive: true, force: true });
10332
10652
  }
10333
10653
  }
10334
10654
  async function planV2(projectDir, config, planV2Dir) {
@@ -10337,14 +10657,14 @@ async function planV2(projectDir, config, planV2Dir) {
10337
10657
  }
10338
10658
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
10339
10659
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
10340
- stagingParentDir: dirname11(planV2Dir)
10660
+ stagingParentDir: dirname12(planV2Dir)
10341
10661
  });
10342
10662
  return resultFromManifest(planV2Dir, manifest);
10343
10663
  }
10344
10664
  function resultFromManifest(planV2Dir, manifest) {
10345
10665
  return {
10346
10666
  planDir: planV2Dir,
10347
- manifestPath: join24(planV2Dir, "plan.json"),
10667
+ manifestPath: join25(planV2Dir, "plan.json"),
10348
10668
  planProtocol: PLAN_PROTOCOL_V2,
10349
10669
  planHash: manifest.planHash,
10350
10670
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -10480,7 +10800,7 @@ function parsePlanV2Manifest(value) {
10480
10800
  return parsed;
10481
10801
  }
10482
10802
  function readPlanV2Manifest(planV2Dir) {
10483
- const manifestPath = join24(planV2Dir, "plan.json");
10803
+ const manifestPath = join25(planV2Dir, "plan.json");
10484
10804
  if (!existsSync17(manifestPath)) {
10485
10805
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
10486
10806
  }
@@ -10503,7 +10823,7 @@ function verifyBlob(planV2Dir, artifact) {
10503
10823
  if (!existsSync17(sourcePath)) {
10504
10824
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
10505
10825
  }
10506
- const stats = statSync6(sourcePath);
10826
+ const stats = statSync7(sourcePath);
10507
10827
  if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
10508
10828
  throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
10509
10829
  }
@@ -10525,17 +10845,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
10525
10845
  artifact,
10526
10846
  sourcePath: verifyBlob(planV2Dir, artifact)
10527
10847
  }));
10528
- mkdirSync11(dirname11(destinationDir), { recursive: true });
10529
- 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-"));
10530
10850
  try {
10531
10851
  for (const { artifact, sourcePath } of verified) {
10532
- const destinationPath = join24(tempDir, ...artifact.path.split("/"));
10533
- mkdirSync11(dirname11(destinationPath), { recursive: true });
10534
- copyFileSync7(sourcePath, destinationPath);
10852
+ const destinationPath = join25(tempDir, ...artifact.path.split("/"));
10853
+ mkdirSync12(dirname12(destinationPath), { recursive: true });
10854
+ copyFileSync8(sourcePath, destinationPath);
10535
10855
  }
10536
10856
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
10537
- writeFileSync8(
10538
- join24(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
10857
+ writeFileSync9(
10858
+ join25(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
10539
10859
  canonicalJsonStringify({
10540
10860
  manifest,
10541
10861
  target,
@@ -10543,9 +10863,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
10543
10863
  }),
10544
10864
  "utf-8"
10545
10865
  );
10546
- renameWithRetry8(tempDir, destinationDir);
10866
+ renameWithRetry9(tempDir, destinationDir);
10547
10867
  } catch (error) {
10548
- rmSync10(tempDir, { recursive: true, force: true });
10868
+ rmSync11(tempDir, { recursive: true, force: true });
10549
10869
  throw error;
10550
10870
  }
10551
10871
  return {
@@ -10558,7 +10878,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
10558
10878
  // Join the artifact's OWN name, not the current constant: a plan written
10559
10879
  // before the container change carries the legacy name, and materializing it
10560
10880
  // under the new one would point at a file that was never written.
10561
- audioPath: target.role === "assembler" ? audioArtifact ? join24(destinationDir, audioArtifact.path) : null : null
10881
+ audioPath: target.role === "assembler" ? audioArtifact ? join25(destinationDir, audioArtifact.path) : null : null
10562
10882
  };
10563
10883
  }
10564
10884
  function assertRemoteSources(remoteSources, artifacts) {
@@ -10577,7 +10897,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
10577
10897
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
10578
10898
  }
10579
10899
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
10580
- const markerPath = join24(planDir, PLAN_V2_MATERIALIZATION_MARKER);
10900
+ const markerPath = join25(planDir, PLAN_V2_MATERIALIZATION_MARKER);
10581
10901
  if (!existsSync17(markerPath)) return null;
10582
10902
  const marker = readJsonFile(markerPath, "materialization marker");
10583
10903
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -10595,8 +10915,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
10595
10915
  assertRemoteSources(remoteSources, required);
10596
10916
  for (const artifact of required) {
10597
10917
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
10598
- const path = join24(planDir, ...artifact.path.split("/"));
10599
- 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) {
10600
10920
  throw new PlanV2IntegrityError(
10601
10921
  `materialized artifact missing or truncated: ${artifact.path}`
10602
10922
  );
@@ -10609,23 +10929,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
10609
10929
  }
10610
10930
 
10611
10931
  // src/services/distributed/planV2Execution.ts
10612
- import { mkdtempSync as mkdtempSync6, rmSync as rmSync13 } from "node:fs";
10932
+ import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
10613
10933
  import { tmpdir as tmpdir2 } from "node:os";
10614
- import { join as join27 } from "node:path";
10934
+ import { join as join28 } from "node:path";
10615
10935
 
10616
10936
  // src/services/distributed/assemble.ts
10617
10937
  import {
10618
10938
  cpSync as cpSync3,
10619
10939
  existsSync as existsSync18,
10620
- mkdirSync as mkdirSync12,
10940
+ mkdirSync as mkdirSync13,
10621
10941
  mkdtempSync as mkdtempSync5,
10622
- readFileSync as readFileSync10,
10942
+ readFileSync as readFileSync11,
10623
10943
  readdirSync as readdirSync8,
10624
- rmSync as rmSync11,
10625
- statSync as statSync7,
10626
- writeFileSync as writeFileSync9
10944
+ rmSync as rmSync12,
10945
+ statSync as statSync8,
10946
+ writeFileSync as writeFileSync10
10627
10947
  } from "node:fs";
10628
- import { dirname as dirname12, join as join25 } from "node:path";
10948
+ import { dirname as dirname13, join as join26 } from "node:path";
10629
10949
  import {
10630
10950
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
10631
10951
  describeFfmpegFailure as describeFfmpegFailure4,
@@ -10633,9 +10953,9 @@ import {
10633
10953
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
10634
10954
  muxVideoWithAudio as muxVideoWithAudio2,
10635
10955
  resolveConfig as resolveConfig4,
10636
- runFfmpeg as runFfmpeg7,
10956
+ runFfmpeg as runFfmpeg8,
10637
10957
  appendSoftwareEncoderThreadsArg,
10638
- renameWithRetry as renameWithRetry9
10958
+ renameWithRetry as renameWithRetry10
10639
10959
  } from "@hyperframes/engine";
10640
10960
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
10641
10961
  function cfrRateControlArgs(locked) {
@@ -10661,13 +10981,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10661
10981
  outputPath,
10662
10982
  durationMs: Date.now() - start,
10663
10983
  framesEncoded: plan2.totalFrames,
10664
- fileSize: statSync7(outputPath).size
10984
+ fileSize: statSync8(outputPath).size
10665
10985
  };
10666
10986
  }
10667
10987
  const format = plan2.dimensions.format;
10668
10988
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
10669
- if (!existsSync18(dirname12(outputPath))) {
10670
- mkdirSync12(dirname12(outputPath), { recursive: true });
10989
+ if (!existsSync18(dirname13(outputPath))) {
10990
+ mkdirSync13(dirname13(outputPath), { recursive: true });
10671
10991
  }
10672
10992
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
10673
10993
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -10689,10 +11009,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10689
11009
  };
10690
11010
  try {
10691
11011
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
10692
- const finalPath = join25(workDir, `output.${format}`);
11012
+ const finalPath = join26(workDir, `output.${format}`);
10693
11013
  const audio = audioPath !== null && existsSync18(audioPath) ? audioPath : null;
10694
11014
  await writeJoined(ctx, videoSources, audio, finalPath);
10695
- renameWithRetry9(finalPath, outputPath);
11015
+ renameWithRetry10(finalPath, outputPath);
10696
11016
  log.info("[assemble] stage timings", {
10697
11017
  ...stageMs,
10698
11018
  chunks: chunkPaths.length,
@@ -10700,7 +11020,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10700
11020
  });
10701
11021
  } finally {
10702
11022
  try {
10703
- rmSync11(workDir, { recursive: true, force: true });
11023
+ rmSync12(workDir, { recursive: true, force: true });
10704
11024
  } catch (err) {
10705
11025
  log.warn("[assemble] failed to remove work dir", {
10706
11026
  workDir,
@@ -10708,7 +11028,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10708
11028
  });
10709
11029
  }
10710
11030
  }
10711
- const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
11031
+ const fileSize = existsSync18(outputPath) ? statSync8(outputPath).size : 0;
10712
11032
  return {
10713
11033
  outputPath,
10714
11034
  durationMs: Date.now() - start,
@@ -10717,18 +11037,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10717
11037
  };
10718
11038
  }
10719
11039
  function readAssemblePlan(planDir, chunkPaths) {
10720
- const planJsonPath = join25(planDir, "plan.json");
10721
- const chunksJsonPath = join25(planDir, "meta", "chunks.json");
11040
+ const planJsonPath = join26(planDir, "plan.json");
11041
+ const chunksJsonPath = join26(planDir, "meta", "chunks.json");
10722
11042
  if (!existsSync18(planJsonPath)) {
10723
11043
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
10724
11044
  }
10725
- const plan2 = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11045
+ const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
10726
11046
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
10727
11047
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
10728
11048
  if (!existsSync18(chunksJsonPath)) {
10729
11049
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
10730
11050
  }
10731
- const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11051
+ const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
10732
11052
  if (chunkPaths.length !== chunks.length) {
10733
11053
  throw new Error(
10734
11054
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -10766,7 +11086,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
10766
11086
  return args;
10767
11087
  }
10768
11088
  async function reencodeCfr(ctx, chunkPaths, encoder) {
10769
- const cfrOutputPath = join25(ctx.workDir, `cfr.${ctx.format}`);
11089
+ const cfrOutputPath = join26(ctx.workDir, `cfr.${ctx.format}`);
10770
11090
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
10771
11091
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
10772
11092
  cfrArgs.push("-y", cfrOutputPath);
@@ -10793,7 +11113,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
10793
11113
  );
10794
11114
  return;
10795
11115
  }
10796
- const paddedAudioPath = join25(ctx.workDir, "audio-padded.m4a");
11116
+ const paddedAudioPath = join26(ctx.workDir, "audio-padded.m4a");
10797
11117
  const padTrimResult = await ctx.timed(
10798
11118
  "audioMs",
10799
11119
  () => padOrTrimAudioToVideoFrameCount({
@@ -10833,9 +11153,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
10833
11153
  }
10834
11154
  }
10835
11155
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
10836
- const listPath = join25(workDir, "concat-list.txt");
11156
+ const listPath = join26(workDir, "concat-list.txt");
10837
11157
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
10838
- writeFileSync9(listPath, `${body}
11158
+ writeFileSync10(listPath, `${body}
10839
11159
  `, "utf-8");
10840
11160
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
10841
11161
  }
@@ -10850,7 +11170,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
10850
11170
  };
10851
11171
  }
10852
11172
  async function runAssembleFfmpeg(args, step, signal, timeout) {
10853
- const result = await runFfmpeg7(args, { signal, timeout });
11173
+ const result = await runFfmpeg8(args, { signal, timeout });
10854
11174
  if (!result.success) {
10855
11175
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
10856
11176
  error: describeFfmpegFailure4(result, timeout),
@@ -10871,13 +11191,13 @@ function stitchesSeparateAudio(planDir) {
10871
11191
  return lockedAudio(planDir).separateAudio === true;
10872
11192
  }
10873
11193
  function planFormat(planDir) {
10874
- const plan2 = JSON.parse(readFileSync10(join25(planDir, "plan.json"), "utf-8"));
11194
+ const plan2 = JSON.parse(readFileSync11(join26(planDir, "plan.json"), "utf-8"));
10875
11195
  return plan2.dimensions.format;
10876
11196
  }
10877
11197
  function lockedAudio(planDir) {
10878
- const encoderJsonPath = join25(planDir, "meta", "encoder.json");
11198
+ const encoderJsonPath = join26(planDir, "meta", "encoder.json");
10879
11199
  if (!existsSync18(encoderJsonPath)) return {};
10880
- return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11200
+ return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
10881
11201
  }
10882
11202
  function readCfrEncoder(planDir, format) {
10883
11203
  if (format !== "mp4") {
@@ -10885,11 +11205,11 @@ function readCfrEncoder(planDir, format) {
10885
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.`
10886
11206
  );
10887
11207
  }
10888
- const encoderJsonPath = join25(planDir, "meta", "encoder.json");
11208
+ const encoderJsonPath = join26(planDir, "meta", "encoder.json");
10889
11209
  if (!existsSync18(encoderJsonPath)) {
10890
11210
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
10891
11211
  }
10892
- const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11212
+ const encoderJson = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
10893
11213
  if (encoderJson.encoder === "libx265-software") {
10894
11214
  throw new Error(
10895
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".`
@@ -10898,11 +11218,11 @@ function readCfrEncoder(planDir, format) {
10898
11218
  return encoderJson;
10899
11219
  }
10900
11220
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
10901
- if (existsSync18(outputPath)) rmSync11(outputPath, { recursive: true, force: true });
10902
- mkdirSync12(outputPath, { recursive: true });
11221
+ if (existsSync18(outputPath)) rmSync12(outputPath, { recursive: true, force: true });
11222
+ mkdirSync13(outputPath, { recursive: true });
10903
11223
  let globalIdx = 0;
10904
11224
  for (const chunkDir of chunkPaths) {
10905
- if (!statSync7(chunkDir).isDirectory()) {
11225
+ if (!statSync8(chunkDir).isDirectory()) {
10906
11226
  throw new Error(
10907
11227
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
10908
11228
  );
@@ -10912,8 +11232,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
10912
11232
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
10913
11233
  }
10914
11234
  for (const frame of frames) {
10915
- const dst = join25(outputPath, formatExportFrameName(globalIdx, "png"));
10916
- cpSync3(join25(chunkDir, frame), dst);
11235
+ const dst = join26(outputPath, formatExportFrameName(globalIdx, "png"));
11236
+ cpSync3(join26(chunkDir, frame), dst);
10917
11237
  globalIdx += 1;
10918
11238
  }
10919
11239
  }
@@ -10923,13 +11243,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
10923
11243
  );
10924
11244
  }
10925
11245
  if (audioPath !== null && existsSync18(audioPath)) {
10926
- const sidecar = join25(outputPath, MIXED_AUDIO_FILENAME4);
11246
+ const sidecar = join26(outputPath, MIXED_AUDIO_FILENAME4);
10927
11247
  cpSync3(audioPath, sidecar);
10928
11248
  }
10929
11249
  let fileSize = 0;
10930
11250
  for (const name of readdirSync8(outputPath)) {
10931
11251
  try {
10932
- fileSize += statSync7(join25(outputPath, name)).size;
11252
+ fileSize += statSync8(join26(outputPath, name)).size;
10933
11253
  } catch {
10934
11254
  }
10935
11255
  }
@@ -10943,9 +11263,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
10943
11263
 
10944
11264
  // src/services/distributed/renderChunk.ts
10945
11265
  import { randomBytes } from "node:crypto";
10946
- 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";
10947
11267
  import { cpus } from "node:os";
10948
- import { join as join26 } from "node:path";
11268
+ import { join as join27 } from "node:path";
10949
11269
  import {
10950
11270
  assertSwiftShader,
10951
11271
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -11064,10 +11384,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
11064
11384
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
11065
11385
  }
11066
11386
  function hashChunkOutput(outputPath, kind) {
11067
- if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11387
+ if (kind === "file") return sha256Hex(readFileSync12(outputPath));
11068
11388
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11069
11389
  const lines = entries.map(
11070
- (name) => `${name}\0${sha256Hex(readFileSync11(join26(outputPath, name)))}`
11390
+ (name) => `${name}\0${sha256Hex(readFileSync12(join27(outputPath, name)))}`
11071
11391
  );
11072
11392
  return sha256Hex(lines.join("\0"));
11073
11393
  }
@@ -11084,16 +11404,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
11084
11404
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11085
11405
  const start = Date.now();
11086
11406
  const log = defaultLogger;
11087
- const planJsonPath = join26(planDir, "plan.json");
11088
- const encoderJsonPath = join26(planDir, "meta", "encoder.json");
11089
- 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");
11090
11410
  if (!existsSync19(planJsonPath)) {
11091
11411
  throw new RenderChunkValidationError(
11092
11412
  MISSING_PLAN_ARTIFACT,
11093
11413
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
11094
11414
  );
11095
11415
  }
11096
- const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
11416
+ const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
11097
11417
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
11098
11418
  const planHashStarted = Date.now();
11099
11419
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -11110,13 +11430,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11110
11430
  );
11111
11431
  }
11112
11432
  }
11113
- const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
11114
- const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11115
- 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);
11116
11436
  let planVideos = null;
11117
11437
  if (existsSync19(videosJsonPath)) {
11118
11438
  try {
11119
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11439
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
11120
11440
  } catch (err) {
11121
11441
  if (err instanceof RenderChunkValidationError) throw err;
11122
11442
  throw new RenderChunkValidationError(
@@ -11144,7 +11464,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11144
11464
  }
11145
11465
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11146
11466
  const framesInChunk = range.endFrame - range.startFrame;
11147
- const compiledDir = join26(planDir, "compiled");
11467
+ const compiledDir = join27(planDir, "compiled");
11148
11468
  if (!existsSync19(compiledDir)) {
11149
11469
  throw new RenderChunkValidationError(
11150
11470
  MISSING_PLAN_ARTIFACT,
@@ -11201,9 +11521,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11201
11521
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
11202
11522
  };
11203
11523
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
11204
- mkdirSync13(workDir, { recursive: true });
11205
- const framesDir = join26(workDir, "captured-frames");
11206
- mkdirSync13(framesDir, { recursive: true });
11524
+ mkdirSync14(workDir, { recursive: true });
11525
+ const framesDir = join27(workDir, "captured-frames");
11526
+ mkdirSync14(framesDir, { recursive: true });
11207
11527
  const videoFramesDir = `${workDir}.video-frames`;
11208
11528
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
11209
11529
  const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -11227,7 +11547,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11227
11547
  dimensions: plan2.dimensions,
11228
11548
  encoder,
11229
11549
  videoCount: planVideos?.videos.length ?? 0,
11230
- html: readFileSync11(join26(compiledDir, "index.html"), "utf-8"),
11550
+ html: readFileSync12(join27(compiledDir, "index.html"), "utf-8"),
11231
11551
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
11232
11552
  skipReadinessVideoIds: deferredVideos.filter(
11233
11553
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -11356,10 +11676,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11356
11676
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
11357
11677
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
11358
11678
  if (isPngSequence) {
11359
- if (!existsSync19(outputChunkPath)) mkdirSync13(outputChunkPath, { recursive: true });
11679
+ if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
11360
11680
  } else {
11361
- const outDir = join26(outputChunkPath, "..");
11362
- if (!existsSync19(outDir)) mkdirSync13(outDir, { recursive: true });
11681
+ const outDir = join27(outputChunkPath, "..");
11682
+ if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
11363
11683
  }
11364
11684
  const encodeInput = {
11365
11685
  job,
@@ -11470,8 +11790,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11470
11790
  return observedChunkCaptureMode(capturePerfs);
11471
11791
  },
11472
11792
  resetForScreenshotRetry: () => {
11473
- rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11474
- mkdirSync13(framesDir, { recursive: true });
11793
+ rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11794
+ mkdirSync14(framesDir, { recursive: true });
11475
11795
  capturePerfs.length = 0;
11476
11796
  captureAttempts.length = 0;
11477
11797
  job.framesRendered = 0;
@@ -11513,8 +11833,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11513
11833
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
11514
11834
  timeoutMs: cfg.ffmpegProcessTimeout
11515
11835
  });
11516
- rmSync12(videoOnlyPath, { force: true });
11517
- rmSync12(audioScratchPath, { force: true });
11836
+ rmSync13(videoOnlyPath, { force: true });
11837
+ rmSync13(audioScratchPath, { force: true });
11518
11838
  }
11519
11839
  encodeStageMs = Date.now() - encodeStarted;
11520
11840
  } finally {
@@ -11561,11 +11881,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11561
11881
  producerVersion: plan2.producerVersion,
11562
11882
  ffmpegVersion
11563
11883
  };
11564
- writeFileSync10(perfPath, `${JSON.stringify(perfPayload, null, 2)}
11884
+ writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
11565
11885
  `, "utf-8");
11566
11886
  try {
11567
- rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11568
- 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 });
11569
11889
  } catch (err) {
11570
11890
  log.warn("[renderChunk] failed to remove work dir", {
11571
11891
  workDir,
@@ -11594,18 +11914,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11594
11914
 
11595
11915
  // src/services/distributed/planV2Execution.ts
11596
11916
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
11597
- const workRoot = mkdtempSync6(join27(tmpdir2(), "hf-plan-v2-chunk-"));
11598
- const materializedPlanDir = join27(workRoot, "plan");
11917
+ const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-chunk-"));
11918
+ const materializedPlanDir = join28(workRoot, "plan");
11599
11919
  try {
11600
11920
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
11601
11921
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
11602
11922
  } finally {
11603
- rmSync13(workRoot, { recursive: true, force: true });
11923
+ rmSync14(workRoot, { recursive: true, force: true });
11604
11924
  }
11605
11925
  }
11606
11926
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
11607
- const workRoot = mkdtempSync6(join27(tmpdir2(), "hf-plan-v2-assembler-"));
11608
- const materializedPlanDir = join27(workRoot, "plan");
11927
+ const workRoot = mkdtempSync6(join28(tmpdir2(), "hf-plan-v2-assembler-"));
11928
+ const materializedPlanDir = join28(workRoot, "plan");
11609
11929
  try {
11610
11930
  const materialized = materializePlanV2Target(
11611
11931
  planV2Dir,
@@ -11620,7 +11940,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
11620
11940
  options
11621
11941
  );
11622
11942
  } finally {
11623
- rmSync13(workRoot, { recursive: true, force: true });
11943
+ rmSync14(workRoot, { recursive: true, force: true });
11624
11944
  }
11625
11945
  }
11626
11946
 
@@ -11869,9 +12189,9 @@ function validateIntDimension(field, value) {
11869
12189
  }
11870
12190
 
11871
12191
  // src/services/distributed/projectHash.ts
11872
- import { closeSync as closeSync4, openSync as openSync4, readdirSync as readdirSync10, readSync as readSync3, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
11873
- import { createHash as createHash6 } from "node:crypto";
11874
- 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";
11875
12195
  var SITE_ID_SCHEME = "site-v2\0";
11876
12196
  var READ_CHUNK_BYTES = 256 * 1024;
11877
12197
  function listSiteFiles(projectDir) {
@@ -11880,11 +12200,11 @@ function listSiteFiles(projectDir) {
11880
12200
  function walk(dir, rel) {
11881
12201
  for (const name of readdirSync10(dir).sort()) {
11882
12202
  if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
11883
- const full = join28(dir, name);
12203
+ const full = join29(dir, name);
11884
12204
  const relPath = rel === "" ? name : `${rel}/${name}`;
11885
12205
  let stat;
11886
12206
  try {
11887
- stat = statSync8(full);
12207
+ stat = statSync9(full);
11888
12208
  } catch (err) {
11889
12209
  throw new Error(`[listSiteFiles] cannot read ${relPath} (dangling symlink?)`, {
11890
12210
  cause: err
@@ -11905,26 +12225,26 @@ function listSiteFiles(projectDir) {
11905
12225
  return files;
11906
12226
  }
11907
12227
  function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
11908
- const hash = createHash6("sha256").update(SITE_ID_SCHEME);
12228
+ const hash = createHash7("sha256").update(SITE_ID_SCHEME);
11909
12229
  const chunk = Buffer.allocUnsafe(READ_CHUNK_BYTES);
11910
12230
  for (const rel of files) {
11911
12231
  hash.update(rel);
11912
12232
  hash.update("\0");
11913
- const fd = openSync4(join28(projectDir, rel), "r");
12233
+ const fd = openSync5(join29(projectDir, rel), "r");
11914
12234
  try {
11915
12235
  let read;
11916
- while ((read = readSync3(fd, chunk, 0, chunk.length, null)) > 0) {
12236
+ while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
11917
12237
  hash.update(chunk.subarray(0, read));
11918
12238
  }
11919
12239
  } finally {
11920
- closeSync4(fd);
12240
+ closeSync5(fd);
11921
12241
  }
11922
12242
  }
11923
12243
  return hash.digest("hex").slice(0, 16);
11924
12244
  }
11925
12245
 
11926
12246
  // src/services/distributed/audioSidecar.ts
11927
- import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
12247
+ import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
11928
12248
  import { extname as extname5 } from "node:path";
11929
12249
  var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
11930
12250
  var AUDIO_SIDECAR_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
@@ -11935,7 +12255,7 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
11935
12255
  const audio = await extractAudioMetadata4(inputPath, { signal }).catch(() => null);
11936
12256
  if (!audio) return await ffmpegIsMissing(signal) ? "unavailable" : "none";
11937
12257
  if (audio.audioStreamCount !== 1 || (audio.audioStreamLeadSeconds ?? 0) > 0) return "none";
11938
- const result = await runFfmpeg8(
12258
+ const result = await runFfmpeg9(
11939
12259
  ["-v", "error", "-y", "-i", inputPath, "-map", "0:a:0", "-vn", "-c:a", "copy"].concat([
11940
12260
  "-movflags",
11941
12261
  "+faststart",
@@ -11949,10 +12269,10 @@ async function buildAudioSidecar(inputPath, outputPath, signal) {
11949
12269
  return isMissing(result) ? "unavailable" : "none";
11950
12270
  }
11951
12271
  var isMissing = (result) => result.error?.code === "ENOENT";
11952
- var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg8(["-version"], { signal }));
12272
+ var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
11953
12273
 
11954
12274
  // src/services/distributed/probeFacts.ts
11955
- import { writeFileSync as writeFileSync11 } from "node:fs";
12275
+ import { writeFileSync as writeFileSync12 } from "node:fs";
11956
12276
  import { extname as extname6 } from "node:path";
11957
12277
  import { buildProbeFactsText } from "@hyperframes/engine";
11958
12278
  var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
@@ -11961,7 +12281,7 @@ function wantsProbeFacts(path) {
11961
12281
  }
11962
12282
  async function buildProbeFacts(inputPath, outputPath, signal) {
11963
12283
  try {
11964
- writeFileSync11(outputPath, await buildProbeFactsText(inputPath, signal));
12284
+ writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
11965
12285
  return "built";
11966
12286
  } catch (error) {
11967
12287
  if (signal?.aborted) throw error;