@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.
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
3
3
  import {
4
4
  existsSync as existsSync16,
5
- mkdirSync as mkdirSync9,
6
- readFileSync as readFileSync8,
5
+ mkdirSync as mkdirSync10,
6
+ readFileSync as readFileSync9,
7
7
  readdirSync as readdirSync5,
8
- rmSync as rmSync11,
9
- statSync as statSync7,
10
- writeFileSync as writeFileSync5,
11
- copyFileSync as copyFileSync4,
8
+ rmSync as rmSync12,
9
+ statSync as statSync8,
10
+ writeFileSync as writeFileSync6,
11
+ copyFileSync as copyFileSync5,
12
12
  appendFileSync
13
13
  } from "fs";
14
14
  import { tmpdir as tmpdir2 } from "node:os";
@@ -56,7 +56,7 @@ import {
56
56
  augmentProtocolTimeoutError,
57
57
  augmentPageNavigationTimeoutError
58
58
  } from "@hyperframes/engine";
59
- import { join as join26, dirname as dirname10, resolve as resolve7 } from "path";
59
+ import { join as join27, dirname as dirname11, resolve as resolve8 } from "path";
60
60
  import { totalmem as totalmem2 } from "node:os";
61
61
  import { randomUUID } from "crypto";
62
62
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -80,6 +80,7 @@ import { Readable } from "node:stream";
80
80
  import { join, extname, resolve as resolve2, sep } from "node:path";
81
81
  import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
82
82
  import { fpsToNumber } from "@hyperframes/core";
83
+ import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
83
84
 
84
85
  // src/services/hyperframeRuntimeLoader.ts
85
86
  import { createHash } from "node:crypto";
@@ -196,11 +197,11 @@ var defaultLogger = createConsoleLogger("info");
196
197
 
197
198
  // src/services/fileServer.ts
198
199
  var readFileAsync = promisify(readFile);
199
- function isPathInside(child2, parent, options = {}) {
200
+ function isPathInside(child3, parent, options = {}) {
200
201
  const { resolveSymlinks = false, pathModule } = options;
201
202
  const resolveFn = pathModule?.resolve ?? resolve2;
202
203
  const separator = pathModule?.sep ?? sep;
203
- const resolvedChild = resolveFn(child2);
204
+ const resolvedChild = resolveFn(child3);
204
205
  const resolvedParent = resolveFn(parent);
205
206
  const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
206
207
  const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
@@ -720,6 +721,18 @@ function createFileServer(options) {
720
721
  html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
721
722
  return c.text(html, 200, { "Content-Type": contentType });
722
723
  }
724
+ if (ext === ".png") {
725
+ const untagged = stripSdrPngColourTags(await readFileAsync(fd));
726
+ if (untagged) {
727
+ const headers = {
728
+ "Content-Type": contentType,
729
+ "Content-Length": String(untagged.length)
730
+ };
731
+ return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
732
+ headers
733
+ });
734
+ }
735
+ }
723
736
  const totalSize = stat.size;
724
737
  const rangeHeader = c.req.header("range");
725
738
  const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
@@ -747,7 +760,8 @@ function createFileServer(options) {
747
760
  const stream = createReadStream(filePath, {
748
761
  fd,
749
762
  autoClose: true,
750
- ...range ? { start: range.start, end: range.end } : {}
763
+ start: range?.start ?? 0,
764
+ ...range ? { end: range.end } : {}
751
765
  });
752
766
  streamOwnsFd = true;
753
767
  try {
@@ -761,10 +775,10 @@ function createFileServer(options) {
761
775
  if (!streamOwnsFd) closeSync(fd);
762
776
  }
763
777
  });
764
- return new Promise((resolve12) => {
778
+ return new Promise((resolve13) => {
765
779
  const connections = /* @__PURE__ */ new Set();
766
780
  const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
767
- resolve12({
781
+ resolve13({
768
782
  url: `http://localhost:${info.port}`,
769
783
  port: info.port,
770
784
  addPreHeadScript: (script) => {
@@ -1271,8 +1285,8 @@ var OrderedRenderEventPublisher = class {
1271
1285
  let timer;
1272
1286
  const drained = await Promise.race([
1273
1287
  this.tail.then(() => true),
1274
- new Promise((resolve12) => {
1275
- timer = setTimeout(() => resolve12(false), FLUSH_TIMEOUT_MS);
1288
+ new Promise((resolve13) => {
1289
+ timer = setTimeout(() => resolve13(false), FLUSH_TIMEOUT_MS);
1276
1290
  })
1277
1291
  ]);
1278
1292
  clearTimeout(timer);
@@ -3616,7 +3630,7 @@ var SequentialCaptureStallError = class extends Error {
3616
3630
  }
3617
3631
  };
3618
3632
  function raceAgainstStall(promise, deadlineMs, input, signal) {
3619
- return new Promise((resolve12, reject) => {
3633
+ return new Promise((resolve13, reject) => {
3620
3634
  const timer = setTimeout(
3621
3635
  () => {
3622
3636
  reject(
@@ -3635,7 +3649,7 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3635
3649
  promise.then(
3636
3650
  (value) => {
3637
3651
  clearTimeout(timer);
3638
- resolve12(value);
3652
+ resolve13(value);
3639
3653
  },
3640
3654
  (error) => {
3641
3655
  clearTimeout(timer);
@@ -5563,15 +5577,15 @@ function countAuthoredTimedClips(html) {
5563
5577
  }
5564
5578
 
5565
5579
  // src/services/render/stages/compileStage.ts
5566
- import { join as join15 } from "node:path";
5580
+ import { join as join16 } from "node:path";
5567
5581
  import { suggestMatchingPreset } from "@hyperframes/core";
5568
5582
 
5569
5583
  // src/services/htmlCompiler.ts
5570
5584
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5571
- import { createHash as createHash5 } from "node:crypto";
5572
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
5573
- import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
5574
- import { parseHTML as parseHTML4 } from "linkedom";
5585
+ import { createHash as createHash6 } from "node:crypto";
5586
+ import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5587
+ import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
5588
+ import { parseHTML as parseHTML5 } from "linkedom";
5575
5589
  import {
5576
5590
  COMPOSITION_SOURCE_URL,
5577
5591
  compileTimingAttrs,
@@ -5609,6 +5623,8 @@ import {
5609
5623
  readDeclaredDefaults,
5610
5624
  parseHostVariableValues,
5611
5625
  headStyleRuns,
5626
+ isAlwaysAppliedStyle,
5627
+ dedupeFontFaceRules,
5612
5628
  inlineScriptRuns,
5613
5629
  parsesAsScript,
5614
5630
  styleElementsFor,
@@ -5845,7 +5861,7 @@ function readLoopOverride(el) {
5845
5861
  }
5846
5862
  return null;
5847
5863
  }
5848
- function resolveGifSourcePath(src, options) {
5864
+ function resolveLocalImageSourcePath(src, options) {
5849
5865
  const trimmed = src.trim();
5850
5866
  if (!trimmed || trimmed.startsWith("data:")) return null;
5851
5867
  const { basePath } = splitUrlSuffix(trimmed);
@@ -5901,12 +5917,12 @@ function resolveClipWindowSeconds(img, compositionDuration) {
5901
5917
  }
5902
5918
  return null;
5903
5919
  }
5904
- function resolvePreparedPlayback(metadata, loop, windowSeconds) {
5920
+ function resolvePreparedPlayback(metadata, loopOverride, windowSeconds) {
5905
5921
  const gifDuration = metadata.durationSeconds > 0 ? metadata.durationSeconds : null;
5906
5922
  let loopIterations = 1;
5907
- if (loop) {
5923
+ if (resolveLoop(metadata, loopOverride)) {
5908
5924
  loopIterations = windowSeconds != null && gifDuration != null ? Math.min(MAX_LOOP_ITERATIONS, Math.max(1, Math.ceil(windowSeconds / gifDuration))) : 1;
5909
- } else if (metadata.loopCount != null && metadata.loopCount > 1) {
5925
+ } else if (loopOverride == null && metadata.loopCount != null && metadata.loopCount > 1) {
5910
5926
  loopIterations = Math.min(MAX_LOOP_ITERATIONS, metadata.loopCount);
5911
5927
  }
5912
5928
  let padSeconds = 0;
@@ -6068,14 +6084,19 @@ async function prepareAnimatedGifInputs(html, options) {
6068
6084
  const img = images[i];
6069
6085
  const src = (img.getAttribute("src") || "").trim();
6070
6086
  if (!hasGifExtension(src)) continue;
6071
- const sourcePath = resolveGifSourcePath(src, options);
6087
+ const sourcePath = resolveLocalImageSourcePath(src, options);
6072
6088
  if (!sourcePath) continue;
6073
6089
  const bytes = readFileSync5(sourcePath);
6074
6090
  const metadata = parseAnimatedGifMetadata(bytes);
6075
6091
  if (!metadata?.animated) continue;
6076
- const loop = resolveLoop(metadata, readLoopOverride(img));
6092
+ const loopOverride = readLoopOverride(img);
6093
+ const loop = resolveLoop(metadata, loopOverride);
6077
6094
  const windowSeconds = resolveClipWindowSeconds(img, compositionDuration);
6078
- const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
6095
+ const { loopIterations, padSeconds } = resolvePreparedPlayback(
6096
+ metadata,
6097
+ loopOverride,
6098
+ windowSeconds
6099
+ );
6079
6100
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
6080
6101
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
6081
6102
  const cachePath = join13(cacheDir, filename);
@@ -6111,19 +6132,74 @@ async function prepareAnimatedGifInputs(html, options) {
6111
6132
  };
6112
6133
  }
6113
6134
 
6114
- // src/services/htmlCompiler.ts
6115
- import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6116
- import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6117
-
6118
- // src/services/assetMediaType.ts
6119
- import { existsSync as existsSync9 } from "node:fs";
6135
+ // src/services/heifImagePrep.ts
6136
+ import { createHash as createHash5 } from "node:crypto";
6120
6137
  import {
6121
- fingerprintElementId,
6122
- isNotMediaPayload,
6123
- NotMediaPayloadError,
6124
- probeMediaProfile,
6125
- resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6126
- } from "@hyperframes/engine";
6138
+ closeSync as closeSync4,
6139
+ copyFileSync as copyFileSync3,
6140
+ mkdirSync as mkdirSync6,
6141
+ openSync as openSync4,
6142
+ readFileSync as readFileSync6,
6143
+ readSync as readSync3,
6144
+ rmSync as rmSync8,
6145
+ statSync as statSync6,
6146
+ writeFileSync as writeFileSync4
6147
+ } from "node:fs";
6148
+ import { dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
6149
+ import { parseHTML as parseHTML4 } from "linkedom";
6150
+ import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
6151
+
6152
+ // src/services/heifRotation.ts
6153
+ function* boxes(buf, start, end) {
6154
+ let at = start;
6155
+ while (at + 8 <= end) {
6156
+ let size = buf.readUInt32BE(at);
6157
+ let header = 8;
6158
+ if (size === 1 && at + 16 <= end) {
6159
+ size = Number(buf.readBigUInt64BE(at + 8));
6160
+ header = 16;
6161
+ } else if (size === 0) {
6162
+ size = end - at;
6163
+ }
6164
+ if (size < header || at + size > end) return;
6165
+ yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
6166
+ at += size;
6167
+ }
6168
+ }
6169
+ function child(buf, parent, type, skip = 0) {
6170
+ return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
6171
+ }
6172
+ function readHeifPrimaryRotation(buf) {
6173
+ try {
6174
+ const root = { type: "", start: 0, end: buf.length };
6175
+ const meta = child(buf, root, "meta")[0];
6176
+ const pitm = meta && child(buf, meta, "pitm", 4)[0];
6177
+ const iprp = meta && child(buf, meta, "iprp", 4)[0];
6178
+ const ipco = iprp && child(buf, iprp, "ipco")[0];
6179
+ if (!pitm || !ipco || !iprp) return 0;
6180
+ const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
6181
+ const props = [...boxes(buf, ipco.start, ipco.end)];
6182
+ for (const ipma of child(buf, iprp, "ipma")) {
6183
+ const version = buf[ipma.start];
6184
+ const wide = (buf[ipma.start + 3] & 1) === 1;
6185
+ let at = ipma.start + 8;
6186
+ for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
6187
+ const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
6188
+ at += version < 1 ? 2 : 4;
6189
+ const count = buf[at++];
6190
+ for (let i = 0; i < count; i++) {
6191
+ const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
6192
+ at += wide ? 2 : 1;
6193
+ const prop = props[index - 1];
6194
+ if (id === primaryId && prop?.type === "irot")
6195
+ return (buf[prop.start] & 3) * 90;
6196
+ }
6197
+ }
6198
+ }
6199
+ } catch {
6200
+ }
6201
+ return 0;
6202
+ }
6127
6203
 
6128
6204
  // src/utils/semaphore.ts
6129
6205
  var Semaphore = class {
@@ -6138,10 +6214,10 @@ var Semaphore = class {
6138
6214
  this.active++;
6139
6215
  return () => this.release();
6140
6216
  }
6141
- return new Promise((resolve12) => {
6217
+ return new Promise((resolve13) => {
6142
6218
  this.queue.push(() => {
6143
6219
  this.active++;
6144
- resolve12(() => this.release());
6220
+ resolve13(() => this.release());
6145
6221
  });
6146
6222
  });
6147
6223
  }
@@ -6172,7 +6248,229 @@ async function withMediaProbeSlot(operation) {
6172
6248
  }
6173
6249
  }
6174
6250
 
6251
+ // src/services/heifImagePrep.ts
6252
+ var PREPARED_HEIF_SUBDIR = "_heif_images";
6253
+ var CACHE_SCHEMA2 = "hfheif-v1";
6254
+ var DEFAULT_TIMEOUT_MS = 12e4;
6255
+ var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
6256
+ var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
6257
+ var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
6258
+ var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
6259
+ var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
6260
+ var HeifImageConversionError = class extends Error {
6261
+ code = HEIF_IMAGE_CONVERSION_FAILED;
6262
+ owner = "user";
6263
+ retryable = false;
6264
+ constructor(label, reason) {
6265
+ super(
6266
+ `Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
6267
+ );
6268
+ this.name = "HeifImageConversionError";
6269
+ }
6270
+ };
6271
+ var HeifToolUnavailableError = class extends Error {
6272
+ code = HEIF_TOOL_UNAVAILABLE;
6273
+ owner = "system";
6274
+ retryable = false;
6275
+ constructor(label, reason) {
6276
+ super(
6277
+ `Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
6278
+ );
6279
+ this.name = "HeifToolUnavailableError";
6280
+ }
6281
+ };
6282
+ var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
6283
+ function isHeifBytes(head) {
6284
+ const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
6285
+ if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
6286
+ const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
6287
+ if (boxEnd < 16) return false;
6288
+ const brands = [bytes.toString("ascii", 8, 12)];
6289
+ for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
6290
+ brands.push(bytes.toString("ascii", offset, offset + 4));
6291
+ }
6292
+ return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
6293
+ }
6294
+ function readHead(path) {
6295
+ let fd;
6296
+ try {
6297
+ fd = openSync4(path, "r");
6298
+ const buf = Buffer.alloc(4096);
6299
+ return buf.subarray(0, readSync3(fd, buf, 0, buf.length, 0));
6300
+ } catch {
6301
+ return null;
6302
+ } finally {
6303
+ if (fd !== void 0) closeSync4(fd);
6304
+ }
6305
+ }
6306
+ async function probeJson(path, withGroups) {
6307
+ const stdout = await runFfprobe(
6308
+ path,
6309
+ withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
6310
+ );
6311
+ return JSON.parse(stdout);
6312
+ }
6313
+ var ROTATION_FILTER = {
6314
+ 0: "",
6315
+ 90: "transpose=2",
6316
+ 180: "hflip,vflip",
6317
+ 270: "transpose=1"
6318
+ };
6319
+ function primaryGrid(probed) {
6320
+ const groups = probed.stream_groups ?? [];
6321
+ return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
6322
+ }
6323
+ async function convertHeifToJpeg(request) {
6324
+ const probed = await probeJson(request.inputPath, true);
6325
+ const grid = primaryGrid(probed);
6326
+ const rotation = readHeifPrimaryRotation(readFileSync6(request.inputPath));
6327
+ const filters = [];
6328
+ let inputs = [];
6329
+ let size = probed.streams?.[0];
6330
+ if (grid?.subcomponents?.length) {
6331
+ size = grid;
6332
+ const layout = grid.subcomponents.map(
6333
+ (t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
6334
+ );
6335
+ inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
6336
+ filters.push(
6337
+ `${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
6338
+ `crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
6339
+ );
6340
+ }
6341
+ if (!size?.width || !size.height) throw new Error("no declared image size");
6342
+ if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
6343
+ const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
6344
+ const result = await runFfmpeg3(
6345
+ [
6346
+ "-hide_banner",
6347
+ "-loglevel",
6348
+ "error",
6349
+ "-i",
6350
+ request.inputPath,
6351
+ ...mapArgs,
6352
+ "-frames:v",
6353
+ "1",
6354
+ "-q:v",
6355
+ "2",
6356
+ "-f",
6357
+ "mjpeg",
6358
+ "-y",
6359
+ request.outputPath
6360
+ ],
6361
+ { timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
6362
+ );
6363
+ if (!result.success) {
6364
+ throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
6365
+ }
6366
+ const out = (await probeJson(request.outputPath, false)).streams?.[0];
6367
+ const same = out?.width === size.width && out.height === size.height;
6368
+ const swapped = out?.width === size.height && out.height === size.width;
6369
+ if (!same && !swapped) {
6370
+ throw new Error(
6371
+ `wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
6372
+ );
6373
+ }
6374
+ }
6375
+ function resolveImageSource(src, options) {
6376
+ const data = DATA_URL_RE.exec(src);
6377
+ if (data) {
6378
+ const bytes = Buffer.from(data[1], "base64");
6379
+ return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
6380
+ }
6381
+ const path = resolveLocalImageSourcePath(src, options);
6382
+ const head = path ? readHead(path) : null;
6383
+ if (!path || !head || !isHeifBytes(head)) return null;
6384
+ return { path, bytes: readFileSync6(path), label: `"${splitUrlSuffix(src).basePath}"` };
6385
+ }
6386
+ function isUsableFile2(path) {
6387
+ try {
6388
+ return statSync6(path).size > 0;
6389
+ } catch {
6390
+ return false;
6391
+ }
6392
+ }
6393
+ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6394
+ mkdirSync6(dirname6(cachePath), { recursive: true });
6395
+ mkdirSync6(dirname6(outputPath), { recursive: true });
6396
+ if (!isUsableFile2(cachePath)) {
6397
+ const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
6398
+ const inlinePath = `${tmpPath}.heic`;
6399
+ const inputPath = source.path ?? inlinePath;
6400
+ try {
6401
+ if (!source.path) writeFileSync4(inlinePath, source.bytes);
6402
+ await (options.transcode ?? convertHeifToJpeg)({
6403
+ inputPath,
6404
+ outputPath: tmpPath,
6405
+ timeoutMs: options.timeoutMs
6406
+ });
6407
+ if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
6408
+ renameWithRetry4(tmpPath, cachePath);
6409
+ } catch (error) {
6410
+ const reason = error instanceof Error ? error.message : String(error);
6411
+ throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
6412
+ } finally {
6413
+ rmSync8(tmpPath, { force: true });
6414
+ rmSync8(inlinePath, { force: true });
6415
+ }
6416
+ }
6417
+ if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
6418
+ copyFileSync3(cachePath, outputPath);
6419
+ }
6420
+ }
6421
+ async function prepareHeifImageInputs(html, options) {
6422
+ const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
6423
+ const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
6424
+ const cacheDir = options.cacheDir ?? outputDir;
6425
+ const preparedAssets = /* @__PURE__ */ new Map();
6426
+ const preparedImages = [];
6427
+ if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
6428
+ const { document: document2 } = parseHTML4(html);
6429
+ const found = [];
6430
+ for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
6431
+ const src = (img.getAttribute("src") || "").trim();
6432
+ const source = src ? resolveImageSource(src, options) : null;
6433
+ if (!source) continue;
6434
+ const hash = createHash5("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
6435
+ found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
6436
+ }
6437
+ if (found.length === 0) return { html, preparedAssets, preparedImages };
6438
+ const unique = new Map(found.map((item) => [item.filename, item.source]));
6439
+ const failed = /* @__PURE__ */ new Set();
6440
+ await Promise.all(
6441
+ [...unique].map(
6442
+ ([filename, source]) => withMediaProbeSlot(
6443
+ () => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
6444
+ ).catch((error) => {
6445
+ if (!options.onFailure) throw error;
6446
+ failed.add(filename);
6447
+ options.onFailure(error);
6448
+ })
6449
+ )
6450
+ );
6451
+ for (const { img, src, filename } of found) {
6452
+ if (failed.has(filename)) continue;
6453
+ const outputSrc = `${outputSrcPrefix}/${filename}`;
6454
+ img.setAttribute("src", outputSrc);
6455
+ preparedAssets.set(outputSrc, join14(outputDir, filename));
6456
+ preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join14(outputDir, filename) });
6457
+ }
6458
+ return { html: document2.toString(), preparedAssets, preparedImages };
6459
+ }
6460
+
6461
+ // src/services/htmlCompiler.ts
6462
+ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6463
+ import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6464
+
6175
6465
  // src/services/assetMediaType.ts
6466
+ import { existsSync as existsSync9 } from "node:fs";
6467
+ import {
6468
+ fingerprintElementId,
6469
+ isNotMediaPayload,
6470
+ NotMediaPayloadError,
6471
+ probeMediaProfile,
6472
+ resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6473
+ } from "@hyperframes/engine";
6176
6474
  var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
6177
6475
  var AssetMediaTypeMismatchError = class extends Error {
6178
6476
  code = ASSET_MEDIA_TYPE_MISMATCH;
@@ -6282,7 +6580,7 @@ function logRemoteDownloadTelemetry(event) {
6282
6580
  }
6283
6581
  var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
6284
6582
  function parseSubCompHtmlForValidity(html) {
6285
- return parseHTML4(html).document;
6583
+ return parseHTML5(html).document;
6286
6584
  }
6287
6585
  function endsWithSourceUrl(code) {
6288
6586
  const trimmed = code.trimEnd();
@@ -6290,7 +6588,7 @@ function endsWithSourceUrl(code) {
6290
6588
  return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
6291
6589
  }
6292
6590
  function prepareCompositionScripts(html) {
6293
- const { document: document2 } = parseHTML4(html);
6591
+ const { document: document2 } = parseHTML5(html);
6294
6592
  for (const el of document2.querySelectorAll("script:not([src])")) {
6295
6593
  const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
6296
6594
  const runsAsScript = isModule || isJavaScriptType(el);
@@ -6327,14 +6625,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
6327
6625
  }
6328
6626
  };
6329
6627
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
6330
- const { document: document2 } = parseHTML4(html);
6628
+ const { document: document2 } = parseHTML5(html);
6331
6629
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
6332
6630
  const problems = [];
6333
6631
  for (const el of hosts) {
6334
6632
  const srcPath = el.getAttribute("data-composition-src");
6335
6633
  if (!srcPath) continue;
6336
6634
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
6337
- const filePath = resolve6(projectDir, srcPath);
6635
+ const filePath = resolve7(projectDir, srcPath);
6338
6636
  if (visited.has(filePath)) continue;
6339
6637
  const read = readProjectFile(filePath);
6340
6638
  if (read.kind === "missing") {
@@ -6390,7 +6688,7 @@ function runtimeRoot(document2) {
6390
6688
  }
6391
6689
  function detectRenderModeHints(html, pageMotionBlur) {
6392
6690
  const reasons = [];
6393
- const { document: document2 } = parseHTML4(html);
6691
+ const { document: document2 } = parseHTML5(html);
6394
6692
  if (document2.querySelector("canvas[layoutsubtree]")) {
6395
6693
  reasons.push({
6396
6694
  code: "htmlInCanvas",
@@ -6432,7 +6730,7 @@ function detectThreeDTransformUsage(html) {
6432
6730
  }
6433
6731
  var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
6434
6732
  function detectAncestorBackgroundImage(html) {
6435
- const { document: document2 } = parseHTML4(html);
6733
+ const { document: document2 } = parseHTML5(html);
6436
6734
  const root = document2.querySelector("[data-composition-id]");
6437
6735
  if (!root) return false;
6438
6736
  const ancestors = [];
@@ -6479,7 +6777,7 @@ function detectShaderTransitionUsage(html) {
6479
6777
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6480
6778
  let filePath = src;
6481
6779
  if (isHttpUrl(src)) {
6482
- if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
6780
+ if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
6483
6781
  try {
6484
6782
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
6485
6783
  onTelemetry: logRemoteDownloadTelemetry
@@ -6489,7 +6787,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6489
6787
  }
6490
6788
  } else {
6491
6789
  filePath = decodedUrlPath(src);
6492
- if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
6790
+ if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
6493
6791
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6494
6792
  }
6495
6793
  if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
@@ -6546,7 +6844,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6546
6844
  function markInferredVariableMediaDurations(html, unresolvedMedia) {
6547
6845
  const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
6548
6846
  if (unresolvedIds.size === 0) return html;
6549
- const { document: document2 } = parseHTML4(html);
6847
+ const { document: document2 } = parseHTML5(html);
6550
6848
  let changed = false;
6551
6849
  for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
6552
6850
  if (!unresolvedIds.has(element.id)) continue;
@@ -6617,13 +6915,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
6617
6915
  }
6618
6916
  async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6619
6917
  const subCompositions = /* @__PURE__ */ new Map();
6620
- const { document: document2 } = parseHTML4(html);
6918
+ const { document: document2 } = parseHTML5(html);
6621
6919
  const compEls = document2.querySelectorAll("[data-composition-src]");
6622
6920
  const workItems = [];
6623
6921
  for (const el of compEls) {
6624
6922
  const srcPath = el.getAttribute("data-composition-src");
6625
6923
  if (!srcPath) continue;
6626
- const filePath = resolve6(projectDir, srcPath);
6924
+ const filePath = resolve7(projectDir, srcPath);
6627
6925
  if (visited.has(filePath)) {
6628
6926
  continue;
6629
6927
  }
@@ -6640,7 +6938,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6640
6938
  workItems.map(async (item) => {
6641
6939
  const { html: compiledSub } = await compileHtmlFile(
6642
6940
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
6643
- dirname6(item.filePath),
6941
+ dirname7(item.filePath),
6644
6942
  downloadDir,
6645
6943
  void 0,
6646
6944
  urlByAbsentLocalPath
@@ -6668,7 +6966,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6668
6966
  return { subCompositions };
6669
6967
  }
6670
6968
  function promoteCssImportsToLinkTags(html) {
6671
- const { document: document2 } = parseHTML4(html);
6969
+ const { document: document2 } = parseHTML5(html);
6672
6970
  const head = document2.querySelector("head");
6673
6971
  if (!head) return html;
6674
6972
  const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
@@ -6737,10 +7035,15 @@ var ProducerHostIdentityMap = class extends Map {
6737
7035
  }
6738
7036
  };
6739
7037
  function coalesceHeadStylesAndBodyScripts(html) {
6740
- const { document: document2 } = parseHTML4(html);
7038
+ const { document: document2 } = parseHTML5(html);
6741
7039
  const head = document2.querySelector("head");
6742
7040
  const body = document2.querySelector("body");
6743
7041
  if (!head && !body) return html;
7042
+ const liveStyles = [...document2.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
7043
+ const dedupedStyles = dedupeFontFaceRules(liveStyles.map((el) => el.textContent || ""));
7044
+ liveStyles.forEach((el, i) => {
7045
+ el.textContent = dedupedStyles[i] ?? "";
7046
+ });
6744
7047
  const styleEls = head ? Array.from(head.querySelectorAll("style")) : [];
6745
7048
  const importRe = /@import\s+url\([^)]*\)\s*;|@import\s+["'][^"']+["']\s*;/gi;
6746
7049
  for (const run of styleEls.length > 1 ? headStyleRuns(styleEls) : []) {
@@ -6783,7 +7086,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
6783
7086
  return document2.toString();
6784
7087
  }
6785
7088
  function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
6786
- const { document: document2 } = parseHTML4(html);
7089
+ const { document: document2 } = parseHTML5(html);
6787
7090
  const head = document2.querySelector("head");
6788
7091
  const body = document2.querySelector("body");
6789
7092
  const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
@@ -6812,17 +7115,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
6812
7115
  resolveHtml: (srcPath) => {
6813
7116
  let compHtml = subCompositions.get(srcPath) || null;
6814
7117
  if (!compHtml) {
6815
- const read = readProjectFile(resolve6(projectDir, srcPath));
7118
+ const read = readProjectFile(resolve7(projectDir, srcPath));
6816
7119
  if (read.kind === "file") {
6817
7120
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
6818
7121
  }
6819
7122
  }
6820
7123
  return compHtml;
6821
7124
  },
6822
- parseHtml: (htmlStr) => parseHTML4(htmlStr).document,
7125
+ parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
6823
7126
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
6824
7127
  // against its own directory, project-root refs stay as authored.
6825
- assetExists: (path) => existsSync10(resolve6(projectDir, path)),
7128
+ assetExists: (path) => existsSync10(resolve7(projectDir, path)),
6826
7129
  // Preserve the authored root wrapper as a child of the host, matching
6827
7130
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
6828
7131
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -6923,7 +7226,7 @@ ${html}
6923
7226
  </html>`;
6924
7227
  }
6925
7228
  function injectTextRenderingRule(html) {
6926
- const { document: document2 } = parseHTML4(html);
7229
+ const { document: document2 } = parseHTML5(html);
6927
7230
  const head = document2.querySelector("head");
6928
7231
  if (!head) return html;
6929
7232
  if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
@@ -6947,7 +7250,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6947
7250
  (alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
6948
7251
  );
6949
7252
  if (!strongest) return true;
6950
- const actual = createHash5(strongest).update(bytes).digest();
7253
+ const actual = createHash6(strongest).update(bytes).digest();
6951
7254
  return hashes.some(
6952
7255
  (hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
6953
7256
  );
@@ -6955,7 +7258,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6955
7258
  async function inlineExternalScripts(html) {
6956
7259
  const fullHtml = ensureFullDocument(html);
6957
7260
  const wrappedFragment = fullHtml !== html;
6958
- const { document: document2 } = parseHTML4(fullHtml);
7261
+ const { document: document2 } = parseHTML5(fullHtml);
6959
7262
  const scripts = document2.querySelectorAll("script[src]");
6960
7263
  const externalScripts = [];
6961
7264
  for (const el of scripts) {
@@ -7004,12 +7307,12 @@ ${safeText}
7004
7307
  return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
7005
7308
  }
7006
7309
  function collectExternalAssets(html, projectDir) {
7007
- const absProjectDir = resolve6(projectDir);
7310
+ const absProjectDir = resolve7(projectDir);
7008
7311
  const externalAssets = /* @__PURE__ */ new Map();
7009
7312
  function processPath(rawPath) {
7010
7313
  const trimmed = rawPath.trim();
7011
7314
  if (isNonRelativeUrl(trimmed)) return null;
7012
- const absPath = resolve6(absProjectDir, trimmed);
7315
+ const absPath = resolve7(absProjectDir, trimmed);
7013
7316
  if (isPathInside2(absPath, absProjectDir)) {
7014
7317
  return null;
7015
7318
  }
@@ -7018,7 +7321,7 @@ function collectExternalAssets(html, projectDir) {
7018
7321
  externalAssets.set(safeKey, absPath);
7019
7322
  return safeKey;
7020
7323
  }
7021
- const { document: document2 } = parseHTML4(html);
7324
+ const { document: document2 } = parseHTML5(html);
7022
7325
  for (const el of document2.querySelectorAll("[src], [href]")) {
7023
7326
  for (const attr of ["src", "href"]) {
7024
7327
  const val = (el.getAttribute(attr) || "").trim();
@@ -7063,7 +7366,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
7063
7366
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
7064
7367
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
7065
7368
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
7066
- if (!existsSync10(remoteDir)) mkdirSync6(remoteDir, { recursive: true });
7369
+ if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
7067
7370
  const urlToLocal = /* @__PURE__ */ new Map();
7068
7371
  await Promise.all(
7069
7372
  [...urlSet].map(async (url) => {
@@ -7110,7 +7413,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
7110
7413
  return downloadAndRewriteUrls(
7111
7414
  urlSet,
7112
7415
  html,
7113
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7416
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7114
7417
  "Remote media download failed",
7115
7418
  "Localized remote media source(s)"
7116
7419
  );
@@ -7125,7 +7428,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
7125
7428
  return downloadAndRewriteUrls(
7126
7429
  urlSet,
7127
7430
  html,
7128
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7431
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7129
7432
  "Remote image download failed",
7130
7433
  "Localized remote image source(s)"
7131
7434
  );
@@ -7141,7 +7444,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
7141
7444
  return downloadAndRewriteUrls(
7142
7445
  urlSet,
7143
7446
  html,
7144
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7447
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7145
7448
  "Remote background-image download failed",
7146
7449
  "Localized remote background-image(s)",
7147
7450
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -7276,7 +7579,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
7276
7579
  return downloadAndRewriteUrls(
7277
7580
  urlSet,
7278
7581
  processed,
7279
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7582
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7280
7583
  "Remote font download failed",
7281
7584
  "Localized remote font face(s)",
7282
7585
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -7321,7 +7624,7 @@ async function embedLocalFontFaces(html, projectDir) {
7321
7624
  while ((urlMatch = urlRe.exec(block)) !== null) {
7322
7625
  const localPath = urlMatch[1];
7323
7626
  if (!localPath || embeddedPaths.has(localPath)) continue;
7324
- const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
7627
+ const absPath = localPath.startsWith("/") ? localPath : resolve7(projectDir, localPath);
7325
7628
  if (!isPathInside2(absPath, projectDir)) continue;
7326
7629
  const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
7327
7630
  try {
@@ -7370,7 +7673,7 @@ function renderGsapDist() {
7370
7673
  }
7371
7674
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
7372
7675
  let gsapDist;
7373
- const exists = (path) => existsSync10(resolve6(projectDir, path));
7676
+ const exists = (path) => existsSync10(resolve7(projectDir, path));
7374
7677
  return html.replace(
7375
7678
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
7376
7679
  (full, prefix, src, file, suffix) => {
@@ -7386,8 +7689,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7386
7689
  if (!isPathInside2(htmlPath, projectDir)) return html;
7387
7690
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
7388
7691
  if (!entryPath.includes("/")) return html;
7389
- const { document: document2 } = parseHTML4(html);
7390
- const assetExists = (path) => existsSync10(resolve6(projectDir, path));
7692
+ const { document: document2 } = parseHTML5(html);
7693
+ const assetExists = (path) => existsSync10(resolve7(projectDir, path));
7391
7694
  rewriteAssetPaths(
7392
7695
  document2.querySelectorAll("[src], [href]"),
7393
7696
  entryPath,
@@ -7409,7 +7712,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7409
7712
  }
7410
7713
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
7411
7714
  const entryHtml = rebaseDirectEntryAssetPaths(
7412
- readFileSync6(htmlPath, "utf-8"),
7715
+ readFileSync7(htmlPath, "utf-8"),
7413
7716
  projectDir,
7414
7717
  htmlPath
7415
7718
  );
@@ -7479,12 +7782,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7479
7782
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
7480
7783
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
7481
7784
  const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
7785
+ const {
7786
+ html: htmlWithJpegs,
7787
+ preparedAssets: preparedHeifAssets,
7788
+ preparedImages: preparedHeifImages
7789
+ } = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
7790
+ projectDir,
7791
+ downloadDir,
7792
+ cacheDir: options.animatedGifCacheDir,
7793
+ sourceAssets: remoteImageAssets,
7794
+ timeoutMs: options.ffmpegProcessTimeout
7795
+ });
7796
+ if (preparedHeifImages.length > 0) {
7797
+ defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
7798
+ }
7482
7799
  const gifSourceAssets = new Map(remoteImageAssets);
7483
7800
  const {
7484
7801
  html: htmlWithPreparedGifs,
7485
7802
  preparedAssets: preparedGifAssets,
7486
7803
  preparedGifs
7487
- } = await prepareAnimatedGifInputs(htmlWithLocalizedFonts, {
7804
+ } = await prepareAnimatedGifInputs(htmlWithJpegs, {
7488
7805
  projectDir,
7489
7806
  downloadDir,
7490
7807
  cacheDir: options.animatedGifCacheDir,
@@ -7511,11 +7828,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7511
7828
  for (const [relPath, absPath] of preparedGifAssets) {
7512
7829
  externalAssets.set(relPath, absPath);
7513
7830
  }
7831
+ for (const [relPath, absPath] of preparedHeifAssets) {
7832
+ externalAssets.set(relPath, absPath);
7833
+ }
7514
7834
  const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
7515
7835
  const masterFxChain = parseMasterFxChain(html);
7516
7836
  for (const video of videos) {
7517
7837
  if (isHttpUrl(video.src)) continue;
7518
- const videoPath = resolve6(projectDir, video.src);
7838
+ const videoPath = resolve7(projectDir, video.src);
7519
7839
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
7520
7840
  (async () => {
7521
7841
  const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
@@ -7535,7 +7855,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7535
7855
  })().catch(() => {
7536
7856
  });
7537
7857
  }
7538
- const { document: document2 } = parseHTML4(html);
7858
+ const { document: document2 } = parseHTML5(html);
7539
7859
  const rootEl = document2.querySelector("[data-composition-id]");
7540
7860
  const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
7541
7861
  const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
@@ -7942,7 +8262,7 @@ async function runCompileStage(input) {
7942
8262
  abortSignal
7943
8263
  } = input;
7944
8264
  const compileStart = Date.now();
7945
- const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
8265
+ const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
7946
8266
  log,
7947
8267
  failClosedFontFetch: failClosedFontFetch === true,
7948
8268
  allowSystemFontCapture,
@@ -7950,7 +8270,7 @@ async function runCompileStage(input) {
7950
8270
  variables: input.variables,
7951
8271
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7952
8272
  pageMotionBlur: input.pageMotionBlur,
7953
- animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
8273
+ animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
7954
8274
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7955
8275
  });
7956
8276
  assertNotAborted();
@@ -8006,7 +8326,7 @@ async function runCompileStage(input) {
8006
8326
  };
8007
8327
  await preflightCompositionAssetMediaTypes({
8008
8328
  projectDir,
8009
- compiledDir: join15(workDir, "compiled"),
8329
+ compiledDir: join16(workDir, "compiled"),
8010
8330
  composition,
8011
8331
  signal: abortSignal
8012
8332
  });
@@ -8051,8 +8371,8 @@ async function runCompileStage(input) {
8051
8371
  }
8052
8372
 
8053
8373
  // src/services/render/stages/probeStage.ts
8054
- import { join as join16 } from "node:path";
8055
- import { parseHTML as parseHTML5 } from "linkedom";
8374
+ import { join as join17 } from "node:path";
8375
+ import { parseHTML as parseHTML6 } from "linkedom";
8056
8376
  import {
8057
8377
  closeCaptureSession as closeCaptureSession4,
8058
8378
  compositionRequiresWebGpu,
@@ -8099,7 +8419,7 @@ function durationToFrameCount(duration, fps) {
8099
8419
  }
8100
8420
  function hasScriptedAudioVolumeAutomation(html, audioCount) {
8101
8421
  if (audioCount <= 0) return false;
8102
- const { document: document2 } = parseHTML5(html);
8422
+ const { document: document2 } = parseHTML6(html);
8103
8423
  const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
8104
8424
  if (!scriptBodies) return false;
8105
8425
  return /\.\s*volume\s*=/i.test(scriptBodies) || /\b(?:gsap|tl|timeline|tween)\s*\.\s*(?:to|fromTo|set)\s*\([\s\S]{0,2000}\bvolume\s*:/i.test(
@@ -8107,12 +8427,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
8107
8427
  );
8108
8428
  }
8109
8429
  function hasAutoStartVideos(html) {
8110
- const { document: document2 } = parseHTML5(html);
8430
+ const { document: document2 } = parseHTML6(html);
8111
8431
  return document2.querySelector("video[data-hf-auto-start]") !== null;
8112
8432
  }
8113
8433
  function hasVariableBoundMedia(html, variables) {
8114
8434
  if (!variables || Object.keys(variables).length === 0) return false;
8115
- const { document: document2 } = parseHTML5(html);
8435
+ const { document: document2 } = parseHTML6(html);
8116
8436
  return Array.from(
8117
8437
  document2.querySelectorAll(
8118
8438
  "img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
@@ -8123,7 +8443,7 @@ function hasVariableBoundMedia(html, variables) {
8123
8443
  });
8124
8444
  }
8125
8445
  function authoredMediaStarts(html) {
8126
- const { document: document2 } = parseHTML5(html);
8446
+ const { document: document2 } = parseHTML6(html);
8127
8447
  const starts = /* @__PURE__ */ new Map();
8128
8448
  for (const media of document2.querySelectorAll("video, audio")) {
8129
8449
  const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
@@ -8160,7 +8480,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
8160
8480
  return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
8161
8481
  }
8162
8482
  function hasRuntimeMediaChanges(html) {
8163
- const { document: document2 } = parseHTML5(html);
8483
+ const { document: document2 } = parseHTML6(html);
8164
8484
  const scriptBodies = [...document2.querySelectorAll("script")].map(
8165
8485
  (script) => script.textContent ?? ""
8166
8486
  );
@@ -8243,7 +8563,7 @@ async function runProbeStage(input) {
8243
8563
  });
8244
8564
  fileServer = await createFileServer({
8245
8565
  projectDir,
8246
- compiledDir: join16(workDir, "compiled"),
8566
+ compiledDir: join17(workDir, "compiled"),
8247
8567
  port: 0,
8248
8568
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8249
8569
  fps: job.config.fps
@@ -8272,7 +8592,7 @@ async function runProbeStage(input) {
8272
8592
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
8273
8593
  probeSession = await createCaptureSession4(
8274
8594
  fileServer.url,
8275
- join16(workDir, "probe"),
8595
+ join17(workDir, "probe"),
8276
8596
  captureOpts,
8277
8597
  null,
8278
8598
  probeCfg
@@ -8357,7 +8677,7 @@ async function runProbeStage(input) {
8357
8677
  });
8358
8678
  probeSession = await createCaptureSession4(
8359
8679
  fileServer.url,
8360
- join16(workDir, "probe-screenshot"),
8680
+ join17(workDir, "probe-screenshot"),
8361
8681
  captureOpts,
8362
8682
  null,
8363
8683
  { ...probeCfg, forceScreenshot: true }
@@ -8393,7 +8713,7 @@ async function runProbeStage(input) {
8393
8713
  compiled,
8394
8714
  resolutions,
8395
8715
  projectDir,
8396
- join16(workDir, "downloads")
8716
+ join17(workDir, "downloads")
8397
8717
  );
8398
8718
  assertNotAborted();
8399
8719
  composition.videos = compiled.videos;
@@ -8584,7 +8904,7 @@ async function runProbeStage(input) {
8584
8904
  }
8585
8905
  await preflightCompositionAssetMediaTypes({
8586
8906
  projectDir,
8587
- compiledDir: join16(workDir, "compiled"),
8907
+ compiledDir: join17(workDir, "compiled"),
8588
8908
  composition,
8589
8909
  signal: abortSignal
8590
8910
  });
@@ -8749,7 +9069,7 @@ function validateDistributedDuration(input) {
8749
9069
 
8750
9070
  // src/services/render/stages/extractVideosStage.ts
8751
9071
  import { existsSync as existsSync11 } from "node:fs";
8752
- import { join as join17 } from "node:path";
9072
+ import { join as join18 } from "node:path";
8753
9073
  import {
8754
9074
  classifyVideoExtractionError,
8755
9075
  createFrameLookupTable,
@@ -9069,14 +9389,14 @@ async function runExtractVideosStage(input) {
9069
9389
  // because short boundary counts can differ by one frame.
9070
9390
  {
9071
9391
  fps: job.config.fps,
9072
- outputDir: join17(compiledDir, "__hyperframes_video_frames"),
9392
+ outputDir: join18(compiledDir, "__hyperframes_video_frames"),
9073
9393
  format: job.config.videoFrameFormat ?? "auto",
9074
9394
  toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
9075
9395
  timelineEnd: composition.duration,
9076
9396
  maxTransientRetries: extractionPolicy.maxTransientRetries,
9077
9397
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
9078
9398
  deferRangeExtraction,
9079
- contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
9399
+ contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
9080
9400
  urlByAbsentLocalPath
9081
9401
  },
9082
9402
  abortSignal,
@@ -9143,8 +9463,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
9143
9463
  }
9144
9464
 
9145
9465
  // src/services/render/stages/audioStage.ts
9146
- import { join as join19 } from "node:path";
9147
- import { rmSync as rmSync9 } from "node:fs";
9466
+ import { join as join20 } from "node:path";
9467
+ import { rmSync as rmSync10 } from "node:fs";
9148
9468
  import {
9149
9469
  MIXED_AUDIO_FILENAME,
9150
9470
  audioFailureDiagnostics,
@@ -9153,8 +9473,8 @@ import {
9153
9473
 
9154
9474
  // src/services/render/audioPadTrim.ts
9155
9475
  import { spawn } from "node:child_process";
9156
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
9157
- import { dirname as dirname7, join as join18 } from "node:path";
9476
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync9 } from "node:fs";
9477
+ import { dirname as dirname8, join as join19 } from "node:path";
9158
9478
  import {
9159
9479
  DEFAULT_CONFIG,
9160
9480
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -9162,8 +9482,8 @@ import {
9162
9482
  extractAudioMetadata as extractAudioMetadata3,
9163
9483
  getFfprobeBinary,
9164
9484
  ManagedChildProcess,
9165
- renameWithRetry as renameWithRetry4,
9166
- runFfmpeg as runFfmpeg4,
9485
+ renameWithRetry as renameWithRetry5,
9486
+ runFfmpeg as runFfmpeg5,
9167
9487
  trackChildProcess
9168
9488
  } from "@hyperframes/engine";
9169
9489
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
@@ -9173,7 +9493,7 @@ import {
9173
9493
  aacSegmentCount,
9174
9494
  describeFfmpegFailure,
9175
9495
  extractAudioMetadata as extractAudioMetadata2,
9176
- runFfmpeg as runFfmpeg3
9496
+ runFfmpeg as runFfmpeg4
9177
9497
  } from "@hyperframes/engine";
9178
9498
  var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
9179
9499
  function checkedDbfs(value) {
@@ -9197,7 +9517,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
9197
9517
  else laterBlocks = Math.max(laterBlocks, block);
9198
9518
  }
9199
9519
  };
9200
- const result = await runFfmpeg3(
9520
+ const result = await runFfmpeg4(
9201
9521
  ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
9202
9522
  { signal, timeout: timeoutMs, onStderr: readLines }
9203
9523
  );
@@ -9357,7 +9677,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
9357
9677
  error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
9358
9678
  };
9359
9679
  } finally {
9360
- for (const path of plan2.cleanupPaths) rmSync8(path, { force: true });
9680
+ for (const path of plan2.cleanupPaths) rmSync9(path, { force: true });
9361
9681
  }
9362
9682
  let loweredDb = 0;
9363
9683
  if (probeTruePeak) {
@@ -9404,8 +9724,8 @@ async function constrainAacTruePeak(input) {
9404
9724
  async function enforceAacTruePeak(input) {
9405
9725
  let scratchDir;
9406
9726
  try {
9407
- scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
9408
- const correctedPath = join18(scratchDir, "audio.m4a");
9727
+ scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
9728
+ const correctedPath = join19(scratchDir, "audio.m4a");
9409
9729
  const measurements = [];
9410
9730
  let attenuationDb = 0;
9411
9731
  let sourcePeakDbfs;
@@ -9420,7 +9740,7 @@ async function enforceAacTruePeak(input) {
9420
9740
  }
9421
9741
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
9422
9742
  if (measuredPath !== correctedPath) return { success: true };
9423
- renameWithRetry4(correctedPath, input.audioPath);
9743
+ renameWithRetry5(correctedPath, input.audioPath);
9424
9744
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
9425
9745
  }
9426
9746
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -9461,7 +9781,7 @@ async function enforceAacTruePeak(input) {
9461
9781
  )}`
9462
9782
  };
9463
9783
  } finally {
9464
- if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
9784
+ if (scratchDir) rmSync9(scratchDir, { recursive: true, force: true });
9465
9785
  }
9466
9786
  }
9467
9787
  function failResult(outputPath, target, source, error) {
@@ -9538,7 +9858,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
9538
9858
  };
9539
9859
  }
9540
9860
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
9541
- return toStepResult(await runFfmpeg4(args, { signal, timeout: timeoutMs }), timeoutMs);
9861
+ return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
9542
9862
  }
9543
9863
  function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
9544
9864
  return async (inputPath, outputPath, durationSeconds, filter) => {
@@ -9608,7 +9928,7 @@ function assertAudioNotInterrupted(failures) {
9608
9928
  }
9609
9929
  async function runAudioStage(input) {
9610
9930
  const stageStart = Date.now();
9611
- const pcmSourcePath = join19(input.workDir, "audio-source.wav");
9931
+ const pcmSourcePath = join20(input.workDir, "audio-source.wav");
9612
9932
  const duration = input.totalFrames * input.fps.den / input.fps.num;
9613
9933
  try {
9614
9934
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
@@ -9628,7 +9948,7 @@ async function runAudioStage(input) {
9628
9948
  }
9629
9949
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9630
9950
  } finally {
9631
- rmSync9(pcmSourcePath, { force: true });
9951
+ rmSync10(pcmSourcePath, { force: true });
9632
9952
  }
9633
9953
  }
9634
9954
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -9646,7 +9966,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9646
9966
  assertNotAborted,
9647
9967
  log
9648
9968
  } = input;
9649
- const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
9969
+ const audioOutputPath = join20(workDir, MIXED_AUDIO_FILENAME);
9650
9970
  let hasAudio = false;
9651
9971
  let audioError;
9652
9972
  let audioFailures;
@@ -9657,7 +9977,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9657
9977
  audioResult = await processCompositionAudio(
9658
9978
  audios,
9659
9979
  projectDir,
9660
- join19(workDir, "audio-work"),
9980
+ join20(workDir, "audio-work"),
9661
9981
  audioOutputPath,
9662
9982
  duration,
9663
9983
  abortSignal,
@@ -9726,7 +10046,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9726
10046
 
9727
10047
  // src/services/render/stages/captureStage.ts
9728
10048
  import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
9729
- import { join as join20 } from "node:path";
10049
+ import { join as join21 } from "node:path";
9730
10050
  import {
9731
10051
  classifyCaptureFailure as classifyCaptureFailure2,
9732
10052
  frameFileExtension,
@@ -9799,13 +10119,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
9799
10119
  if (!existsSync12(workDir)) return total;
9800
10120
  for (const attempt of readdirSync3(workDir)) {
9801
10121
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
9802
- total += sampleDirectoryBytes(join20(workDir, attempt));
10122
+ total += sampleDirectoryBytes(join21(workDir, attempt));
9803
10123
  continue;
9804
10124
  }
9805
10125
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
9806
- const attemptDir = join20(workDir, attempt);
10126
+ const attemptDir = join21(workDir, attempt);
9807
10127
  for (const name of readdirSync3(attemptDir)) {
9808
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
10128
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join21(attemptDir, name));
9809
10129
  }
9810
10130
  }
9811
10131
  return total;
@@ -10098,8 +10418,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
10098
10418
  }
10099
10419
 
10100
10420
  // src/services/render/stages/captureHdrStage.ts
10101
- import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
10102
- import { join as join24 } from "node:path";
10421
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
10422
+ import { join as join25 } from "node:path";
10103
10423
  import {
10104
10424
  closeCaptureSession as closeCaptureSession7,
10105
10425
  cloneCaptureWarnings as cloneCaptureWarnings2,
@@ -10171,7 +10491,7 @@ function createHdrImageTransferCache(options = {}) {
10171
10491
  }
10172
10492
 
10173
10493
  // src/services/render/stages/captureHdrSequentialLoop.ts
10174
- import { join as join21 } from "node:path";
10494
+ import { join as join22 } from "node:path";
10175
10495
  import {
10176
10496
  TRANSITIONS,
10177
10497
  crossfade
@@ -10296,7 +10616,7 @@ async function runSequentialLayeredFrameLoop(input) {
10296
10616
  );
10297
10617
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10298
10618
  writeFileExclusiveSync(
10299
- join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10619
+ join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10300
10620
  normalCanvas
10301
10621
  );
10302
10622
  }
@@ -10328,7 +10648,7 @@ async function runSequentialLayeredFrameLoop(input) {
10328
10648
  }
10329
10649
 
10330
10650
  // src/services/render/stages/captureHdrHybridLoop.ts
10331
- import { join as join23 } from "node:path";
10651
+ import { join as join24 } from "node:path";
10332
10652
  import {
10333
10653
  TRANSITIONS as TRANSITIONS2,
10334
10654
  closeCaptureSession as closeCaptureSession6,
@@ -10342,7 +10662,7 @@ import {
10342
10662
  // src/services/shaderTransitionWorkerPool.ts
10343
10663
  import { Worker } from "node:worker_threads";
10344
10664
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
10345
- import { dirname as dirname8, join as join22 } from "node:path";
10665
+ import { dirname as dirname9, join as join23 } from "node:path";
10346
10666
  import { createRequire } from "node:module";
10347
10667
  import { existsSync as existsSync13 } from "node:fs";
10348
10668
  import { cpus as cpus2 } from "node:os";
@@ -10355,10 +10675,10 @@ function resolveWorkerEntry(explicit) {
10355
10675
  const isTs = override.endsWith(".ts");
10356
10676
  return { path: override, isTs };
10357
10677
  }
10358
- const moduleDir = dirname8(fileURLToPath3(import.meta.url));
10359
- const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
10678
+ const moduleDir = dirname9(fileURLToPath3(import.meta.url));
10679
+ const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
10360
10680
  if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
10361
- const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
10681
+ const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
10362
10682
  return { path: tsPath, isTs: true };
10363
10683
  }
10364
10684
  function buildExecArgv(entryIsTs) {
@@ -10501,8 +10821,8 @@ async function createShaderTransitionWorkerPool(opts) {
10501
10821
  if (terminated) {
10502
10822
  throw new Error("shader-blend pool already terminated");
10503
10823
  }
10504
- return new Promise((resolve12, reject) => {
10505
- const task = traceEnabled ? { req, resolve: resolve12, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve12, reject };
10824
+ return new Promise((resolve13, reject) => {
10825
+ const task = traceEnabled ? { req, resolve: resolve13, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve13, reject };
10506
10826
  const idle = slots.find((s) => !s.busy && !s.dead);
10507
10827
  if (idle) {
10508
10828
  queue.unshift(task);
@@ -10726,7 +11046,7 @@ async function runHybridLayeredFrameLoop(input) {
10726
11046
  );
10727
11047
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10728
11048
  writeFileExclusiveSync(
10729
- join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11049
+ join24(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10730
11050
  canvas
10731
11051
  );
10732
11052
  }
@@ -10914,9 +11234,9 @@ async function runCaptureHdrStage(input) {
10914
11234
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
10915
11235
  }
10916
11236
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
10917
- const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
11237
+ const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
10918
11238
  if (debugDumpDir && !existsSync14(debugDumpDir)) {
10919
- mkdirSync7(debugDumpDir, { recursive: true });
11239
+ mkdirSync8(debugDumpDir, { recursive: true });
10920
11240
  }
10921
11241
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
10922
11242
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -11069,24 +11389,24 @@ async function runCaptureHdrStage(input) {
11069
11389
 
11070
11390
  // src/services/render/stages/encodeStage.ts
11071
11391
  import {
11072
- copyFileSync as copyFileSync3,
11392
+ copyFileSync as copyFileSync4,
11073
11393
  existsSync as existsSync15,
11074
- mkdirSync as mkdirSync8,
11394
+ mkdirSync as mkdirSync9,
11075
11395
  readdirSync as readdirSync4,
11076
- readFileSync as readFileSync7,
11077
- rmSync as rmSync10,
11078
- statSync as statSync6,
11079
- writeFileSync as writeFileSync4
11396
+ readFileSync as readFileSync8,
11397
+ rmSync as rmSync11,
11398
+ statSync as statSync7,
11399
+ writeFileSync as writeFileSync5
11080
11400
  } from "node:fs";
11081
11401
  import { readFile as readFile2 } from "node:fs/promises";
11082
- import { dirname as dirname9, join as join25 } from "node:path";
11402
+ import { dirname as dirname10, join as join26 } from "node:path";
11083
11403
  import {
11084
11404
  encodeFramesChunkedConcat,
11085
11405
  encodeFramesFromDir,
11086
11406
  formatFfmpegError,
11087
11407
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
11088
11408
  resolveConfig,
11089
- runFfmpeg as runFfmpeg5,
11409
+ runFfmpeg as runFfmpeg6,
11090
11410
  spawnStreamingEncoder as spawnStreamingEncoder4
11091
11411
  } from "@hyperframes/engine";
11092
11412
  import {
@@ -11162,17 +11482,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
11162
11482
  }
11163
11483
  async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
11164
11484
  const { outputPath } = argsInput;
11165
- const result = await runFfmpeg5(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11485
+ const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11166
11486
  if (!result.success) {
11167
11487
  const error = formatFfmpegError(result.exitCode, result.stderr);
11168
11488
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11169
11489
  }
11170
- const gif = readFileSync7(outputPath);
11490
+ const gif = readFileSync8(outputPath);
11171
11491
  if (!clearGifFramesBeforeNext(gif)) {
11172
11492
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
11173
11493
  return gifEncodeFailure(startTime, outputPath, error);
11174
11494
  }
11175
- writeFileSync4(outputPath, gif);
11495
+ writeFileSync5(outputPath, gif);
11176
11496
  return null;
11177
11497
  }
11178
11498
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -11194,17 +11514,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11194
11514
  try {
11195
11515
  const run = { signal: input.signal, timeout: input.timeout };
11196
11516
  for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
11197
- const result = await runFfmpeg5(args, run);
11517
+ const result = await runFfmpeg6(args, run);
11198
11518
  if (!result.success) {
11199
11519
  const error = formatFfmpegError(result.exitCode, result.stderr);
11200
11520
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11201
11521
  }
11202
11522
  }
11203
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
11523
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
11204
11524
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
11205
11525
  if (failure) return failure;
11206
11526
  }
11207
- const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
11527
+ const fileSize = existsSync15(outputPath) ? statSync7(outputPath).size : 0;
11208
11528
  return {
11209
11529
  success: true,
11210
11530
  outputPath,
@@ -11213,7 +11533,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11213
11533
  fileSize
11214
11534
  };
11215
11535
  } finally {
11216
- rmSync10(input.palettePath, { force: true });
11536
+ rmSync11(input.palettePath, { force: true });
11217
11537
  }
11218
11538
  }
11219
11539
  function startEncodeProgress(job, stage, onProgress) {
@@ -11285,10 +11605,10 @@ function encodeWhileCapturing(input, totalFrames) {
11285
11605
  const stream = await encoder;
11286
11606
  for (let next = 0; next < totalFrames; next++) {
11287
11607
  while (!landed.has(next) && !captureDone && !signal.aborted) {
11288
- await new Promise((resolve12) => wake = resolve12);
11608
+ await new Promise((resolve13) => wake = resolve13);
11289
11609
  }
11290
11610
  if (signal.aborted) return;
11291
- const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
11611
+ const mergedPath = join26(input.framesDir, formatCaptureFrameName(next, "jpg"));
11292
11612
  const frame = await readLandedFrame(landed.get(next), mergedPath);
11293
11613
  landed.delete(next);
11294
11614
  if (!await stream.writeFrame(frame)) {
@@ -11363,7 +11683,7 @@ async function runEncodeStage(input) {
11363
11683
  const stage5Start = Date.now();
11364
11684
  if (isPngSequence) {
11365
11685
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
11366
- if (!existsSync15(outputPath)) mkdirSync8(outputPath, { recursive: true });
11686
+ if (!existsSync15(outputPath)) mkdirSync9(outputPath, { recursive: true });
11367
11687
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
11368
11688
  if (captured.length === 0) {
11369
11689
  throw new Error(
@@ -11371,11 +11691,11 @@ async function runEncodeStage(input) {
11371
11691
  );
11372
11692
  }
11373
11693
  captured.forEach((name, i) => {
11374
- const dst = join25(outputPath, formatExportFrameName(i, "png"));
11375
- copyFileSync3(join25(framesDir, name), dst);
11694
+ const dst = join26(outputPath, formatExportFrameName(i, "png"));
11695
+ copyFileSync4(join26(framesDir, name), dst);
11376
11696
  });
11377
11697
  if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
11378
- copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
11698
+ copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
11379
11699
  log.info(
11380
11700
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
11381
11701
  );
@@ -11395,7 +11715,7 @@ async function runEncodeStage(input) {
11395
11715
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
11396
11716
  fps: job.config.fps,
11397
11717
  loop,
11398
- palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
11718
+ palettePath: join26(dirname10(videoOnlyPath), "gif-palette.png"),
11399
11719
  preserveAlpha: needsAlpha,
11400
11720
  signal: abortSignal,
11401
11721
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -11605,9 +11925,9 @@ function sampleDirectoryBytes(dir) {
11605
11925
  continue;
11606
11926
  }
11607
11927
  for (const name of entries) {
11608
- const full = join26(current, name);
11928
+ const full = join27(current, name);
11609
11929
  try {
11610
- const st = statSync7(full);
11930
+ const st = statSync8(full);
11611
11931
  if (st.isDirectory()) {
11612
11932
  stack.push(full);
11613
11933
  } else if (st.isFile()) {
@@ -11754,8 +12074,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11754
12074
  const ranges = [];
11755
12075
  let rangeStart = null;
11756
12076
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11757
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11758
- const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
12077
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12078
+ const missing = !existsSync16(framePath) || statSync8(framePath).size <= 8;
11759
12079
  if (missing && rangeStart === null) {
11760
12080
  rangeStart = frameIndex;
11761
12081
  } else if (!missing && rangeStart !== null) {
@@ -11771,7 +12091,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11771
12091
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
11772
12092
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
11773
12093
  const batches = [];
11774
- const outputDirFor = (batchIndex, workerId) => join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
12094
+ const outputDirFor = (batchIndex, workerId) => join27(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
11775
12095
  if (stride > 1) {
11776
12096
  const spans = /* @__PURE__ */ new Map();
11777
12097
  for (const range of ranges) {
@@ -11831,8 +12151,8 @@ function getNextRetryWorkerCount(currentWorkers) {
11831
12151
  return Math.max(1, Math.floor(currentWorkers / 2));
11832
12152
  }
11833
12153
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
11834
- if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
11835
- return join26(dirname10(outputPath), `work-${jobId}-`);
12154
+ if (platform2 === "win32" || platform2 === "darwin") return join27(systemTempDir, "hf-render-");
12155
+ return join27(dirname11(outputPath), `work-${jobId}-`);
11836
12156
  }
11837
12157
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
11838
12158
  function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
@@ -11874,7 +12194,7 @@ The composition is too large for the available memory. To reduce memory pressure
11874
12194
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
11875
12195
  let captured = 0;
11876
12196
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11877
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12197
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11878
12198
  if (existsSync16(framePath)) captured++;
11879
12199
  }
11880
12200
  return captured;
@@ -11900,7 +12220,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11900
12220
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
11901
12221
  });
11902
12222
  pendingTransientRetry = false;
11903
- const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
12223
+ const attemptWorkDir = join27(options.workDir, `capture-attempt-${attempt}`);
11904
12224
  const batches = missingRanges ? buildMissingFrameRetryBatches(
11905
12225
  missingRanges,
11906
12226
  currentWorkers,
@@ -12506,18 +12826,18 @@ function deVerifyFallbackTelemetry(err) {
12506
12826
  };
12507
12827
  }
12508
12828
  function resolveRenderDebugDir() {
12509
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
12510
- return join26(producerRoot, ".debug");
12829
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname11(fileURLToPath4(import.meta.url)), "../..");
12830
+ return join27(producerRoot, ".debug");
12511
12831
  }
12512
12832
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
12513
12833
  validateHlsRenderConfig(job.config);
12514
12834
  const debugDir = resolveRenderDebugDir();
12515
- const outputDir = dirname10(outputPath);
12516
- if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
12517
- const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12835
+ const outputDir = dirname11(outputPath);
12836
+ if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
12837
+ const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12518
12838
  const pipelineStart = Date.now();
12519
12839
  const baseLog = job.config.logger ?? defaultLogger;
12520
- const logPath = job.config.debug ? join26(workDir, "render.log") : null;
12840
+ const logPath = job.config.debug ? join27(workDir, "render.log") : null;
12521
12841
  const execution = new RenderExecutionContext({
12522
12842
  request: { renderJobId: job.id, projectDir, outputPath },
12523
12843
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -12531,7 +12851,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
12531
12851
  log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
12532
12852
  return;
12533
12853
  }
12534
- rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12854
+ rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12535
12855
  });
12536
12856
  try {
12537
12857
  await executeRenderPipeline({
@@ -12577,7 +12897,7 @@ async function executeRenderPipeline(input) {
12577
12897
  imageDecodeFailures: 0
12578
12898
  };
12579
12899
  let hdrPerf;
12580
- const perfOutputPath = join26(workDir, "perf-summary.json");
12900
+ const perfOutputPath = join27(workDir, "perf-summary.json");
12581
12901
  const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
12582
12902
  const observability = new RenderObservabilityRecorder({
12583
12903
  pipelineStartMs: pipelineStart,
@@ -12650,7 +12970,7 @@ async function executeRenderPipeline(input) {
12650
12970
  job.startedAt = /* @__PURE__ */ new Date();
12651
12971
  assertNotAborted();
12652
12972
  assertConfiguredFfmpegBinariesExist();
12653
- if (!existsSync16(workDir)) mkdirSync9(workDir, { recursive: true });
12973
+ if (!existsSync16(workDir)) mkdirSync10(workDir, { recursive: true });
12654
12974
  if (job.config.debug) {
12655
12975
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
12656
12976
  }
@@ -12679,22 +12999,22 @@ async function executeRenderPipeline(input) {
12679
12999
  requestedWorkers: job.config.workers ?? "auto"
12680
13000
  });
12681
13001
  const entryFile = job.config.entryFile || "index.html";
12682
- let htmlPath = join26(projectDir, entryFile);
13002
+ let htmlPath = join27(projectDir, entryFile);
12683
13003
  if (!existsSync16(htmlPath)) {
12684
13004
  throw new Error(`Entry file not found: ${htmlPath}`);
12685
13005
  }
12686
13006
  assertNotAborted();
12687
- const rawEntry = readFileSync8(htmlPath, "utf-8");
13007
+ const rawEntry = readFileSync9(htmlPath, "utf-8");
12688
13008
  if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
12689
- const wrapperPath = join26(workDir, "standalone-entry.html");
12690
- const projectIndexPath = join26(projectDir, "index.html");
13009
+ const wrapperPath = join27(workDir, "standalone-entry.html");
13010
+ const projectIndexPath = join27(projectDir, "index.html");
12691
13011
  if (!existsSync16(projectIndexPath)) {
12692
13012
  throw new Error(
12693
13013
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
12694
13014
  );
12695
13015
  }
12696
13016
  const standaloneHtml = extractStandaloneEntryFromIndex(
12697
- readFileSync8(projectIndexPath, "utf-8"),
13017
+ readFileSync9(projectIndexPath, "utf-8"),
12698
13018
  entryFile,
12699
13019
  rawEntry
12700
13020
  );
@@ -12703,7 +13023,7 @@ async function executeRenderPipeline(input) {
12703
13023
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
12704
13024
  );
12705
13025
  }
12706
- writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
13026
+ writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
12707
13027
  htmlPath = wrapperPath;
12708
13028
  log.info("Extracted standalone entry from index.html host context", {
12709
13029
  entryFile
@@ -12850,7 +13170,7 @@ async function executeRenderPipeline(input) {
12850
13170
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
12851
13171
  code: "extract_video"
12852
13172
  });
12853
- const compiledDir = join26(workDir, "compiled");
13173
+ const compiledDir = join27(workDir, "compiled");
12854
13174
  const extractResult = await observeRenderStage(
12855
13175
  observability,
12856
13176
  "video_extract",
@@ -12984,7 +13304,7 @@ async function executeRenderPipeline(input) {
12984
13304
  try {
12985
13305
  fileServer = await createFileServer({
12986
13306
  projectDir,
12987
- compiledDir: join26(workDir, "compiled"),
13307
+ compiledDir: join27(workDir, "compiled"),
12988
13308
  port: 0,
12989
13309
  preHeadScripts: [VIRTUAL_TIME_SHIM],
12990
13310
  fps: job.config.fps
@@ -13002,8 +13322,8 @@ async function executeRenderPipeline(input) {
13002
13322
  if (!activeFileServer) {
13003
13323
  throw new Error("File server failed to initialize before frame capture");
13004
13324
  }
13005
- const framesDir = join26(workDir, "captured-frames");
13006
- if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
13325
+ const framesDir = join27(workDir, "captured-frames");
13326
+ if (!existsSync16(framesDir)) mkdirSync10(framesDir, { recursive: true });
13007
13327
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
13008
13328
  code: "start_browsers"
13009
13329
  });
@@ -13474,7 +13794,7 @@ async function executeRenderPipeline(input) {
13474
13794
  hls: ".mp4"
13475
13795
  };
13476
13796
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
13477
- const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
13797
+ const videoOnlyPath = join27(workDir, `video-only${videoExt}`);
13478
13798
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
13479
13799
  if (usePageSideCompositingForTransitions) {
13480
13800
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -13727,7 +14047,7 @@ async function executeRenderPipeline(input) {
13727
14047
  pageMotionBlur: job.config.pageMotionBlur,
13728
14048
  captureQuality: captureOptions.quality
13729
14049
  });
13730
- const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
14050
+ const segmentDir = segmentDirFor(join27(projectDir, "renders"), segmentPlanHash);
13731
14051
  let completedSegments = /* @__PURE__ */ new Set();
13732
14052
  if (job.config.resumeSegments === true) {
13733
14053
  const existing = readSegmentManifest(segmentDir);
@@ -13742,10 +14062,10 @@ async function executeRenderPipeline(input) {
13742
14062
  });
13743
14063
  }
13744
14064
  if (completedSegments.size === 0) {
13745
- rmSync11(segmentDir, { recursive: true, force: true });
14065
+ rmSync12(segmentDir, { recursive: true, force: true });
13746
14066
  }
13747
14067
  } else {
13748
- rmSync11(segmentDir, { recursive: true, force: true });
14068
+ rmSync12(segmentDir, { recursive: true, force: true });
13749
14069
  }
13750
14070
  const segmentManifest = readSegmentManifest(segmentDir) ?? {
13751
14071
  version: 1,
@@ -13826,7 +14146,7 @@ async function executeRenderPipeline(input) {
13826
14146
  );
13827
14147
  updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
13828
14148
  if (job.config.keepSegments !== true) {
13829
- rmSync11(segmentDir, { recursive: true, force: true });
14149
+ rmSync12(segmentDir, { recursive: true, force: true });
13830
14150
  }
13831
14151
  } else {
13832
14152
  capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
@@ -14098,8 +14418,8 @@ async function executeRenderPipeline(input) {
14098
14418
  "capture_disk",
14099
14419
  "drawElement capture failed; retrying with a fresh screenshot session"
14100
14420
  );
14101
- rmSync11(framesDir, { recursive: true, force: true });
14102
- mkdirSync9(framesDir, { recursive: true });
14421
+ rmSync12(framesDir, { recursive: true, force: true });
14422
+ mkdirSync10(framesDir, { recursive: true });
14103
14423
  resetCaptureAttemptProgress(job);
14104
14424
  dedupPerfs.length = 0;
14105
14425
  cfg.useDrawElement = false;
@@ -14275,7 +14595,7 @@ async function executeRenderPipeline(input) {
14275
14595
  job.perfSummary = perfSummary;
14276
14596
  if (job.config.debug) {
14277
14597
  try {
14278
- writeFileSync5(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14598
+ writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14279
14599
  } catch (err) {
14280
14600
  log.debug("Failed to write perf summary", {
14281
14601
  perfOutputPath,
@@ -14285,8 +14605,8 @@ async function executeRenderPipeline(input) {
14285
14605
  }
14286
14606
  if (job.config.debug) {
14287
14607
  if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
14288
- const debugOutput = join26(workDir, `output${videoExt}`);
14289
- copyFileSync4(stagedOutputPath, debugOutput);
14608
+ const debugOutput = join27(workDir, `output${videoExt}`);
14609
+ copyFileSync5(stagedOutputPath, debugOutput);
14290
14610
  }
14291
14611
  }
14292
14612
  await commitArtifactTransaction(artifactTransaction, () => {
@@ -14410,15 +14730,15 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
14410
14730
  // src/services/distributed/plan.ts
14411
14731
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
14412
14732
  import {
14413
- copyFileSync as copyFileSync5,
14733
+ copyFileSync as copyFileSync6,
14414
14734
  cpSync as cpSync2,
14415
14735
  existsSync as existsSync21,
14416
14736
  linkSync,
14417
- mkdirSync as mkdirSync13,
14418
- rmSync as rmSync13,
14419
- writeFileSync as writeFileSync8
14737
+ mkdirSync as mkdirSync14,
14738
+ rmSync as rmSync14,
14739
+ writeFileSync as writeFileSync9
14420
14740
  } from "node:fs";
14421
- import { extname as extname6, isAbsolute as isAbsolute3, join as join34, relative as relative5, sep as sep2 } from "node:path";
14741
+ import { extname as extname6, isAbsolute as isAbsolute3, join as join35, relative as relative5, sep as sep2 } from "node:path";
14422
14742
  import {
14423
14743
  fpsToNumber as fpsToNumber10,
14424
14744
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -14428,12 +14748,12 @@ import {
14428
14748
  normalizeVp9CpuUsed,
14429
14749
  resolveConfig as resolveConfig3,
14430
14750
  declareContentAddressedUrl,
14431
- renameWithRetry as renameWithRetry6,
14751
+ renameWithRetry as renameWithRetry7,
14432
14752
  declareProbeFactsUrl
14433
14753
  } from "@hyperframes/engine";
14434
14754
 
14435
14755
  // src/services/distributed/planFrameCost.ts
14436
- import { join as join29 } from "node:path";
14756
+ import { join as join30 } from "node:path";
14437
14757
  import {
14438
14758
  closeCaptureSession as closeCaptureSession9,
14439
14759
  createCaptureSession as createCaptureSession9,
@@ -14443,7 +14763,7 @@ import {
14443
14763
 
14444
14764
  // src/services/distributed/chunkFrames.ts
14445
14765
  import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
14446
- import { extname as extname4, join as join28 } from "node:path";
14766
+ import { extname as extname4, join as join29 } from "node:path";
14447
14767
  import {
14448
14768
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
14449
14769
  createFrameLookupTable as createFrameLookupTable2,
@@ -14453,8 +14773,8 @@ import {
14453
14773
 
14454
14774
  // src/services/distributed/shared.ts
14455
14775
  import { execFile as execFileCallback } from "node:child_process";
14456
- import { dirname as dirname11, join as join27 } from "node:path";
14457
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
14776
+ import { dirname as dirname12, join as join28 } from "node:path";
14777
+ import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
14458
14778
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14459
14779
  import { promisify as promisify2 } from "node:util";
14460
14780
  import {
@@ -14469,7 +14789,7 @@ function isPlanAudioArtifactPath(path) {
14469
14789
  }
14470
14790
  function resolvePlanAudioPath(planDir) {
14471
14791
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
14472
- const candidate = join27(planDir, name);
14792
+ const candidate = join28(planDir, name);
14473
14793
  if (existsSync17(candidate)) return candidate;
14474
14794
  }
14475
14795
  return null;
@@ -14726,13 +15046,13 @@ function buildSyntheticRenderJob(input) {
14726
15046
  var cachedProducerVersion = null;
14727
15047
  function readProducerVersion() {
14728
15048
  if (cachedProducerVersion !== null) return cachedProducerVersion;
14729
- const startDir = dirname11(fileURLToPath5(import.meta.url));
15049
+ const startDir = dirname12(fileURLToPath5(import.meta.url));
14730
15050
  let current = startDir;
14731
15051
  for (let i = 0; i < 10; i++) {
14732
- const candidate = join27(current, "package.json");
15052
+ const candidate = join28(current, "package.json");
14733
15053
  if (existsSync17(candidate)) {
14734
15054
  try {
14735
- const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
15055
+ const pkg = JSON.parse(readFileSync10(candidate, "utf-8"));
14736
15056
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
14737
15057
  cachedProducerVersion = pkg.version;
14738
15058
  return pkg.version;
@@ -14740,7 +15060,7 @@ function readProducerVersion() {
14740
15060
  } catch {
14741
15061
  }
14742
15062
  }
14743
- const parent = dirname11(current);
15063
+ const parent = dirname12(current);
14744
15064
  if (parent === current) break;
14745
15065
  current = parent;
14746
15066
  }
@@ -14783,7 +15103,7 @@ function frameNumberFromFileName(name) {
14783
15103
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
14784
15104
  const result = [];
14785
15105
  for (const v of videos) {
14786
- const outputDir = join28(planDir, "video-frames", v.videoId);
15106
+ const outputDir = join29(planDir, "video-frames", v.videoId);
14787
15107
  if (!existsSync18(outputDir)) {
14788
15108
  throw new Error(
14789
15109
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
@@ -14802,7 +15122,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
14802
15122
  if (!frameName) continue;
14803
15123
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
14804
15124
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
14805
- framePaths.set(frameIndex, join28(outputDir, frameName));
15125
+ framePaths.set(frameIndex, join29(outputDir, frameName));
14806
15126
  }
14807
15127
  result.push({
14808
15128
  videoId: v.videoId,
@@ -14892,14 +15212,14 @@ async function extractDeferredVideoFramesForChunk(input) {
14892
15212
  const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
14893
15213
  return Promise.all(
14894
15214
  deferred.map(async (video) => {
14895
- const outputDir = join28(input.outputRoot, video.videoId);
15215
+ const outputDir = join29(input.outputRoot, video.videoId);
14896
15216
  const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
14897
15217
  if (indexes.length === 0) {
14898
15218
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
14899
15219
  }
14900
15220
  const range = video.deferredRange;
14901
15221
  const remoteUrl = remoteSources[range.sourcePath];
14902
- const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
15222
+ const sourcePath = remoteUrl ?? join29(input.planDir, ...range.sourcePath.split("/"));
14903
15223
  if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
14904
15224
  throw new RenderChunkValidationError(
14905
15225
  MISSING_PLAN_ARTIFACT,
@@ -14948,12 +15268,12 @@ async function measurePlanSecondsPerFrame(input) {
14948
15268
  planVideos,
14949
15269
  frames: sampled,
14950
15270
  fps: fpsRatio,
14951
- outputRoot: join29(workDir, "frame-cost-video-frames"),
15271
+ outputRoot: join30(workDir, "frame-cost-video-frames"),
14952
15272
  cfg: input.cfg
14953
15273
  })
14954
15274
  ];
14955
15275
  const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
14956
- const compiledDir = join29(planDir, "compiled");
15276
+ const compiledDir = join30(planDir, "compiled");
14957
15277
  const fileServer = await createFileServer({
14958
15278
  projectDir: compiledDir,
14959
15279
  compiledDir,
@@ -14965,7 +15285,7 @@ async function measurePlanSecondsPerFrame(input) {
14965
15285
  try {
14966
15286
  session = await createCaptureSession9(
14967
15287
  fileServer.url,
14968
- join29(workDir, "frame-cost"),
15288
+ join30(workDir, "frame-cost"),
14969
15289
  buildChunkCaptureOptions({
14970
15290
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
14971
15291
  encoder: input,
@@ -15062,8 +15382,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
15062
15382
  }
15063
15383
 
15064
15384
  // src/services/render/stages/freezePlan.ts
15065
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
15066
- import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
15385
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
15386
+ import { join as join31, relative as relative3, resolve as resolve9 } from "node:path";
15067
15387
 
15068
15388
  // src/services/distributed/planProtocol.ts
15069
15389
  var PLAN_SCHEMA_VERSION = 1;
@@ -15161,12 +15481,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
15161
15481
  }
15162
15482
 
15163
15483
  // src/services/render/stages/planHash.ts
15164
- import { createHash as createHash6 } from "node:crypto";
15165
- import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
15484
+ import { createHash as createHash7 } from "node:crypto";
15485
+ import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync5, readSync as readSync4 } from "node:fs";
15166
15486
  var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
15167
15487
  var FIELD_DELIMITER = Buffer.from([0]);
15168
15488
  function computePlanHash(input) {
15169
- const hash = createHash6("sha256");
15489
+ const hash = createHash7("sha256");
15170
15490
  hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
15171
15491
  hash.update(input.compositionHtml);
15172
15492
  hash.update(FIELD_DELIMITER);
@@ -15213,27 +15533,27 @@ function canonicalJsonStringify(value) {
15213
15533
  throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
15214
15534
  }
15215
15535
  function sha256Hex(bytes) {
15216
- const h = createHash6("sha256");
15536
+ const h = createHash7("sha256");
15217
15537
  h.update(bytes);
15218
15538
  return h.digest("hex");
15219
15539
  }
15220
15540
  function sha256File(path) {
15221
- const hash = createHash6("sha256");
15541
+ const hash = createHash7("sha256");
15222
15542
  const buffer = Buffer.allocUnsafe(1024 * 1024);
15223
- const fd = openSync4(path, "r");
15543
+ const fd = openSync5(path, "r");
15224
15544
  try {
15225
15545
  let bytesRead = 0;
15226
15546
  do {
15227
- bytesRead = readSync3(fd, buffer, 0, buffer.byteLength, null);
15547
+ bytesRead = readSync4(fd, buffer, 0, buffer.byteLength, null);
15228
15548
  if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
15229
15549
  } while (bytesRead > 0);
15230
15550
  } finally {
15231
- closeSync4(fd);
15551
+ closeSync5(fd);
15232
15552
  }
15233
15553
  return hash.digest("hex");
15234
15554
  }
15235
15555
  async function sha256FileAsync(path) {
15236
- const hash = createHash6("sha256");
15556
+ const hash = createHash7("sha256");
15237
15557
  for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
15238
15558
  hash.update(chunk);
15239
15559
  }
@@ -15277,11 +15597,11 @@ function stripUndefined(value) {
15277
15597
  }
15278
15598
  function listPlanFiles(planDir) {
15279
15599
  const results = [];
15280
- const rootResolved = resolve8(planDir);
15600
+ const rootResolved = resolve9(planDir);
15281
15601
  function walk(dir) {
15282
15602
  const entries = readdirSync7(dir, { withFileTypes: true });
15283
15603
  for (const entry of entries) {
15284
- const full = join30(dir, entry.name);
15604
+ const full = join31(dir, entry.name);
15285
15605
  if (entry.isDirectory()) {
15286
15606
  walk(full);
15287
15607
  } else if (entry.isFile()) {
@@ -15302,7 +15622,7 @@ function collectPlanAssetShas(planDir, digests) {
15302
15622
  const assets = [];
15303
15623
  for (const file of files) {
15304
15624
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
15305
- compositionHtml = readFileSync10(file.absolutePath);
15625
+ compositionHtml = readFileSync11(file.absolutePath);
15306
15626
  continue;
15307
15627
  }
15308
15628
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -15322,23 +15642,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
15322
15642
  function writePlanRemoteFiles(planDir, files) {
15323
15643
  if (files.length === 0) return;
15324
15644
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
15325
- writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
15645
+ writeFileSync7(join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
15326
15646
  }
15327
15647
  function readPlanRemoteFiles(planDir) {
15328
- const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
15329
- return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
15648
+ const path = join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
15649
+ return existsSync19(path) ? JSON.parse(readFileSync11(path, "utf-8")) : [];
15330
15650
  }
15331
15651
  function recomputePlanHashFromPlanDir(planDir, digests) {
15332
- const planJsonPath = join30(planDir, "plan.json");
15333
- const encoderJsonPath = join30(planDir, "meta", "encoder.json");
15652
+ const planJsonPath = join31(planDir, "plan.json");
15653
+ const encoderJsonPath = join31(planDir, "meta", "encoder.json");
15334
15654
  if (!existsSync19(planJsonPath)) {
15335
15655
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
15336
15656
  }
15337
15657
  if (!existsSync19(encoderJsonPath)) {
15338
15658
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
15339
15659
  }
15340
- const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
15341
- const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
15660
+ const planJson = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
15661
+ const encoderConfigCanonicalJson = readFileSync11(encoderJsonPath, "utf-8");
15342
15662
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
15343
15663
  return computePlanHash({
15344
15664
  compositionHtml,
@@ -15367,18 +15687,18 @@ async function freezePlan(input) {
15367
15687
  if (!existsSync19(planDir)) {
15368
15688
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
15369
15689
  }
15370
- const metaDir = join30(planDir, "meta");
15371
- if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
15372
- writeFileSync6(
15373
- join30(metaDir, "composition.json"),
15690
+ const metaDir = join31(planDir, "meta");
15691
+ if (!existsSync19(metaDir)) mkdirSync11(metaDir, { recursive: true });
15692
+ writeFileSync7(
15693
+ join31(metaDir, "composition.json"),
15374
15694
  `${JSON.stringify(composition, null, 2)}
15375
15695
  `,
15376
15696
  "utf-8"
15377
15697
  );
15378
15698
  const encoderForCanonical = stripUndefined(encoder);
15379
15699
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
15380
- writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
15381
- writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
15700
+ writeFileSync7(join31(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
15701
+ writeFileSync7(join31(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
15382
15702
  `, "utf-8");
15383
15703
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
15384
15704
  const planHash = computePlanHash({
@@ -15402,8 +15722,8 @@ async function freezePlan(input) {
15402
15722
  duration: durationSeconds,
15403
15723
  hasAudio
15404
15724
  };
15405
- const planJsonPath = join30(planDir, "plan.json");
15406
- writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
15725
+ const planJsonPath = join31(planDir, "plan.json");
15726
+ writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
15407
15727
  `, "utf-8");
15408
15728
  return { planJsonPath, planHash };
15409
15729
  }
@@ -15444,16 +15764,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
15444
15764
  }
15445
15765
 
15446
15766
  // src/services/distributed/chunkAudio.ts
15447
- import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
15448
- import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
15449
- import { join as join32 } from "node:path";
15450
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
15767
+ import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync12 } from "node:fs";
15768
+ import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
15769
+ import { join as join33 } from "node:path";
15770
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
15451
15771
 
15452
15772
  // src/services/distributed/mp4Stitch.ts
15453
- import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync12 } from "node:fs";
15773
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync13 } from "node:fs";
15454
15774
  import { open } from "node:fs/promises";
15455
- import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
15456
- import { dirname as dirname12, join as join31 } from "node:path";
15775
+ import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
15776
+ import { dirname as dirname13, join as join32 } from "node:path";
15457
15777
  import { finished, pipeline } from "node:stream/promises";
15458
15778
  function boxAt(src, off, at, limit) {
15459
15779
  let size = src.readUInt32BE(off);
@@ -15466,16 +15786,16 @@ function boxAt(src, off, at, limit) {
15466
15786
  return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
15467
15787
  }
15468
15788
  function childBoxes(buf, parent) {
15469
- const boxes = [];
15470
- for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
15471
- boxes.push(boxAt(buf, at, at, parent.end));
15789
+ const boxes2 = [];
15790
+ for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
15791
+ boxes2.push(boxAt(buf, at, at, parent.end));
15472
15792
  }
15473
- return boxes;
15793
+ return boxes2;
15474
15794
  }
15475
15795
  function findChild(buf, parent, type) {
15476
15796
  return childBoxes(buf, parent).find((box2) => box2.type === type);
15477
15797
  }
15478
- function child(buf, parent, type) {
15798
+ function child2(buf, parent, type) {
15479
15799
  const found = findChild(buf, parent, type);
15480
15800
  if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
15481
15801
  return found;
@@ -15527,7 +15847,7 @@ function rows(buf, table, columns, countAt = 4) {
15527
15847
  function readMediaTime(moov, trak) {
15528
15848
  const edts = findChild(moov, trak, "edts");
15529
15849
  if (!edts) return 0;
15530
- const elst = child(moov, edts, "elst");
15850
+ const elst = child2(moov, edts, "elst");
15531
15851
  const wide = moov[elst.bodyStart] === 1;
15532
15852
  const entries = moov.readUInt32BE(elst.bodyStart + 4);
15533
15853
  for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
@@ -15542,7 +15862,7 @@ function readSizes(moov, stsz) {
15542
15862
  if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
15543
15863
  return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
15544
15864
  }
15545
- function readSync4(moov, stbl, sampleCount) {
15865
+ function readSync5(moov, stbl, sampleCount) {
15546
15866
  const stss = findChild(moov, stbl, "stss");
15547
15867
  if (!stss) return null;
15548
15868
  const sync = Array(sampleCount).fill(false);
@@ -15552,10 +15872,10 @@ function readSync4(moov, stbl, sampleCount) {
15552
15872
  function readChunkOffsets(moov, stbl) {
15553
15873
  const stco = findChild(moov, stbl, "stco");
15554
15874
  if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
15555
- return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
15875
+ return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
15556
15876
  }
15557
15877
  function readSamplesPerChunk(moov, stbl, chunkCount) {
15558
- const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
15878
+ const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
15559
15879
  return stsc.flatMap(([firstChunk, perChunk, description], i) => {
15560
15880
  if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
15561
15881
  const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
@@ -15563,28 +15883,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
15563
15883
  });
15564
15884
  }
15565
15885
  function readTrack(moov, trak) {
15566
- const mdia = child(moov, trak, "mdia");
15567
- const hdlr = child(moov, mdia, "hdlr");
15568
- const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
15886
+ const mdia = child2(moov, trak, "mdia");
15887
+ const hdlr = child2(moov, mdia, "hdlr");
15888
+ const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
15569
15889
  if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
15570
- const stts = rows(moov, child(moov, stbl, "stts"), 2);
15890
+ const stts = rows(moov, child2(moov, stbl, "stts"), 2);
15571
15891
  const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
15572
- const sizes = readSizes(moov, child(moov, stbl, "stsz"));
15892
+ const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
15573
15893
  const chunkOffsets = readChunkOffsets(moov, stbl);
15574
15894
  const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
15575
15895
  if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
15576
15896
  throw new Error("[mp4Stitch] sample tables disagree on the sample count");
15577
15897
  }
15578
- const stsd = child(moov, stbl, "stsd");
15898
+ const stsd = child2(moov, stbl, "stsd");
15579
15899
  return {
15580
15900
  trak,
15581
15901
  handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
15582
- timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
15902
+ timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
15583
15903
  mediaTime: readMediaTime(moov, trak),
15584
15904
  stsd: moov.subarray(stsd.start, stsd.end),
15585
15905
  durations,
15586
15906
  sizes,
15587
- sync: readSync4(moov, stbl, sizes.length),
15907
+ sync: readSync5(moov, stbl, sizes.length),
15588
15908
  samplesPerChunk,
15589
15909
  chunkOffsets
15590
15910
  };
@@ -15725,9 +16045,9 @@ function buildStitchedHeader(layouts, audio) {
15725
16045
  }
15726
16046
  const template = layouts[0].moov;
15727
16047
  const root = boxAt(template, 0, 0, template.length);
15728
- const mvhd = child(template, root, "mvhd");
16048
+ const mvhd = child2(template, root, "mvhd");
15729
16049
  const movieTimescale = timescaleOf(template, mvhd);
15730
- const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child(template, track.trak, "tkhd")));
16050
+ const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
15731
16051
  const audioTrackId = Math.max(...chunkTrackIds) + 1;
15732
16052
  const buildMoov = (dataStart2) => {
15733
16053
  const traks = plan2.map((track, t) => {
@@ -15817,18 +16137,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15817
16137
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
15818
16138
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
15819
16139
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
15820
- mkdirSync11(dirname12(outputPath), { recursive: true });
16140
+ mkdirSync12(dirname13(outputPath), { recursive: true });
15821
16141
  const workDir = mkdtempSync4(`${outputPath}.stitch-`);
15822
- const out = createWriteStream(join31(workDir, "out.mp4"));
16142
+ const out = createWriteStream(join32(workDir, "out.mp4"));
15823
16143
  try {
15824
16144
  out.write(header);
15825
16145
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
15826
16146
  out.end();
15827
16147
  await finished(out);
15828
- renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
16148
+ renameWithRetry6(join32(workDir, "out.mp4"), outputPath);
15829
16149
  } finally {
15830
16150
  out.destroy();
15831
- rmSync12(workDir, { recursive: true, force: true });
16151
+ rmSync13(workDir, { recursive: true, force: true });
15832
16152
  }
15833
16153
  }
15834
16154
 
@@ -15917,9 +16237,9 @@ async function writeChunkAudioSlices(input) {
15917
16237
  offset += track.sizes[offsets.length - 1];
15918
16238
  }
15919
16239
  });
15920
- const bytes = readFileSync11(input.audioPath);
16240
+ const bytes = readFileSync12(input.audioPath);
15921
16241
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
15922
- mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
16242
+ mkdirSync13(join33(input.planDir, SLICE_DIR), { recursive: true });
15923
16243
  input.chunks.forEach((_, index) => {
15924
16244
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
15925
16245
  if (!cut) return;
@@ -15927,18 +16247,18 @@ async function writeChunkAudioSlices(input) {
15927
16247
  const start = offsets[cut.firstPacket];
15928
16248
  layout.partStarts[index] = start;
15929
16249
  const end = offsets[last] + track.sizes[last];
15930
- writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
16250
+ writeFileSync8(join33(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
15931
16251
  });
15932
16252
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
15933
- writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
15934
- writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
16253
+ writeFileSync8(join33(input.planDir, SLICE_FRAME), Buffer.concat(frame));
16254
+ writeFileSync8(join33(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
15935
16255
  }
15936
16256
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15937
- const layoutPath = join32(planDir, SLICE_LAYOUT);
16257
+ const layoutPath = join33(planDir, SLICE_LAYOUT);
15938
16258
  if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
15939
- const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
15940
- const frame = readFileSync11(join32(planDir, SLICE_FRAME));
15941
- const fd = openSync5(scratchPath, "w");
16259
+ const layout = JSON.parse(readFileSync12(layoutPath, "utf-8"));
16260
+ const frame = readFileSync12(join33(planDir, SLICE_FRAME));
16261
+ const fd = openSync6(scratchPath, "w");
15942
16262
  try {
15943
16263
  writeSync(fd, frame, 0, layout.payloadStart, 0);
15944
16264
  writeSync(
@@ -15950,12 +16270,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15950
16270
  );
15951
16271
  const start = layout.partStarts[chunkIndex];
15952
16272
  if (start !== void 0) {
15953
- const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
16273
+ const part = readFileSync12(join33(planDir, chunkAudioSlicePath(chunkIndex)));
15954
16274
  writeSync(fd, part, 0, part.length, start);
15955
16275
  }
15956
16276
  ftruncateSync(fd, layout.sizeBytes);
15957
16277
  } finally {
15958
- closeSync5(fd);
16278
+ closeSync6(fd);
15959
16279
  }
15960
16280
  return scratchPath;
15961
16281
  }
@@ -15975,7 +16295,7 @@ async function muxChunkWithAudio(input) {
15975
16295
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
15976
16296
  appendRenderProvenanceArgs(args, input.outputPath);
15977
16297
  args.push("-y", input.outputPath);
15978
- const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
16298
+ const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
15979
16299
  if (!result.success) {
15980
16300
  throw new Error(
15981
16301
  `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
@@ -16004,9 +16324,9 @@ async function muxChunkWithAudio(input) {
16004
16324
  }
16005
16325
 
16006
16326
  // src/services/distributed/planSize.ts
16007
- import { createHash as createHash7 } from "node:crypto";
16327
+ import { createHash as createHash8 } from "node:crypto";
16008
16328
  import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
16009
- import { extname as extname5, join as join33, relative as relative4 } from "node:path";
16329
+ import { extname as extname5, join as join34, relative as relative4 } from "node:path";
16010
16330
  var MAX_TOP_COMPONENTS = 10;
16011
16331
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
16012
16332
  ".aac",
@@ -16034,7 +16354,7 @@ var PLAN_ROOT_CATEGORY = {
16034
16354
  "plan.json": "metadata"
16035
16355
  };
16036
16356
  function hashComponent(value) {
16037
- return createHash7("sha256").update(value).digest("hex").slice(0, 12);
16357
+ return createHash8("sha256").update(value).digest("hex").slice(0, 12);
16038
16358
  }
16039
16359
  function classifyFile(segments, rootKind) {
16040
16360
  const rootName = segments[0] ?? "";
@@ -16118,7 +16438,7 @@ function walkRegularFiles(dir, visit) {
16118
16438
  return;
16119
16439
  }
16120
16440
  for (const entry of entries) {
16121
- const full = join33(dir, entry.name);
16441
+ const full = join34(dir, entry.name);
16122
16442
  if (entry.isDirectory()) {
16123
16443
  walkRegularFiles(full, visit);
16124
16444
  } else if (entry.isFile()) {
@@ -16330,13 +16650,13 @@ function assertPositiveInteger2(name, value) {
16330
16650
  }
16331
16651
  var FREEZE_OWNED_PLAN_FILES = [
16332
16652
  "plan.json",
16333
- join34("meta", "composition.json"),
16334
- join34("meta", "encoder.json"),
16335
- join34("meta", "chunks.json")
16653
+ join35("meta", "composition.json"),
16654
+ join35("meta", "encoder.json"),
16655
+ join35("meta", "chunks.json")
16336
16656
  ];
16337
16657
  function removeFreezeOwnedPlanFiles(planDir) {
16338
16658
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
16339
- rmSync13(join34(planDir, relativePath), { force: true });
16659
+ rmSync14(join35(planDir, relativePath), { force: true });
16340
16660
  }
16341
16661
  }
16342
16662
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -16481,23 +16801,23 @@ function createVideoSourceShipper(input) {
16481
16801
  const known = shipped.get(srcPath);
16482
16802
  if (known) return known;
16483
16803
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
16484
- const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
16804
+ const current = stagedRel === null ? srcPath : join35(input.planDir, "video-frames", stagedRel);
16485
16805
  let result = null;
16486
16806
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
16487
16807
  const rel = pathInside(root, srcPath);
16488
- if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16808
+ if (rel !== null && (existsSync21(join35(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16489
16809
  result = ["compiled", ...rel.split(sep2)].join("/");
16490
16810
  break;
16491
16811
  }
16492
16812
  }
16493
16813
  if (result === null) {
16494
16814
  result = `video-sources/${shipped.size}${extname6(current)}`;
16495
- const destination = join34(input.planDir, ...result.split("/"));
16496
- mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
16815
+ const destination = join35(input.planDir, ...result.split("/"));
16816
+ mkdirSync14(join35(input.planDir, "video-sources"), { recursive: true });
16497
16817
  try {
16498
16818
  linkSync(current, destination);
16499
16819
  } catch {
16500
- copyFileSync5(current, destination);
16820
+ copyFileSync6(current, destination);
16501
16821
  }
16502
16822
  }
16503
16823
  shipped.set(srcPath, result);
@@ -16513,7 +16833,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16513
16833
  useGpu: false,
16514
16834
  browserGpuMode: "software"
16515
16835
  });
16516
- if (!existsSync21(planDir)) mkdirSync13(planDir, { recursive: true });
16836
+ if (!existsSync21(planDir)) mkdirSync14(planDir, { recursive: true });
16517
16837
  const log = config.logger ?? defaultLogger;
16518
16838
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
16519
16839
  const abortSignal = config.abortSignal;
@@ -16542,15 +16862,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16542
16862
  variables: config.variables
16543
16863
  });
16544
16864
  const entryFile = config.entryFile ?? "index.html";
16545
- const htmlPath = join34(projectDir, entryFile);
16865
+ const htmlPath = join35(projectDir, entryFile);
16546
16866
  if (!existsSync21(htmlPath)) {
16547
16867
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
16548
16868
  }
16549
- const workDir = join34(planDir, ".plan-work");
16550
- rmSync13(workDir, { recursive: true, force: true });
16551
- mkdirSync13(workDir, { recursive: true });
16552
- const compiledDir = join34(workDir, "compiled");
16553
- mkdirSync13(compiledDir, { recursive: true });
16869
+ const workDir = join35(planDir, ".plan-work");
16870
+ rmSync14(workDir, { recursive: true, force: true });
16871
+ mkdirSync14(workDir, { recursive: true });
16872
+ const compiledDir = join35(workDir, "compiled");
16873
+ mkdirSync14(compiledDir, { recursive: true });
16554
16874
  let stageStarted = Date.now();
16555
16875
  cpSync2(projectDir, compiledDir, {
16556
16876
  recursive: true,
@@ -16563,10 +16883,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16563
16883
  }
16564
16884
  });
16565
16885
  logPlanStage(log, "copy", stageStarted);
16566
- const finalCompiledDir = join34(planDir, "compiled");
16886
+ const finalCompiledDir = join35(planDir, "compiled");
16567
16887
  const needsAlpha = outputNeedsAlpha(config.format);
16568
16888
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
16569
- const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
16889
+ const remoteLocalPath = (rel) => join35(projectDir, ...rel.split("/"));
16570
16890
  for (const [, file] of remoteFiles) {
16571
16891
  declareContentAddressedUrl(file.url, file.sha256);
16572
16892
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -16752,18 +17072,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16752
17072
  let stageStarted = Date.now();
16753
17073
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
16754
17074
  logPlanStage(log, "extract", stageStarted);
16755
- const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
16756
- const videoFramesDst = join34(planDir, "video-frames");
16757
- if (existsSync21(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
16758
- const videoSourcesDir = join34(planDir, "video-sources");
16759
- rmSync13(videoSourcesDir, { recursive: true, force: true });
17075
+ const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
17076
+ const videoFramesDst = join35(planDir, "video-frames");
17077
+ if (existsSync21(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
17078
+ const videoSourcesDir = join35(planDir, "video-sources");
17079
+ rmSync14(videoSourcesDir, { recursive: true, force: true });
16760
17080
  if (existsSync21(stagedVideoFrames)) {
16761
- renameWithRetry6(stagedVideoFrames, videoFramesDst);
17081
+ renameWithRetry7(stagedVideoFrames, videoFramesDst);
16762
17082
  } else {
16763
- mkdirSync13(videoFramesDst, { recursive: true });
17083
+ mkdirSync14(videoFramesDst, { recursive: true });
16764
17084
  }
16765
- if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
16766
- renameWithRetry6(compiledDir, finalCompiledDir);
17085
+ if (existsSync21(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
17086
+ renameWithRetry7(compiledDir, finalCompiledDir);
16767
17087
  const shipVideoSource = createVideoSourceShipper({
16768
17088
  planDir,
16769
17089
  projectDir,
@@ -16792,7 +17112,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16792
17112
  };
16793
17113
  })
16794
17114
  });
16795
- mkdirSync13(join34(planDir, "meta"), { recursive: true });
17115
+ mkdirSync14(join35(planDir, "meta"), { recursive: true });
16796
17116
  writePlanRemoteFiles(
16797
17117
  planDir,
16798
17118
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -16801,8 +17121,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16801
17121
  sizeBytes
16802
17122
  }))
16803
17123
  );
16804
- writeFileSync8(
16805
- join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
17124
+ writeFileSync9(
17125
+ join35(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
16806
17126
  JSON.stringify(planVideosJson, null, 2),
16807
17127
  "utf-8"
16808
17128
  );
@@ -16881,9 +17201,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16881
17201
  if (audioResult?.audioError) {
16882
17202
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
16883
17203
  }
16884
- const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
17204
+ const planAudioPath = join35(planDir, PLAN_AUDIO_RELATIVE_PATH);
16885
17205
  if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
16886
- renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
17206
+ renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
16887
17207
  }
16888
17208
  if (chunksCarryAudio && existsSync21(planAudioPath)) {
16889
17209
  if (options.sliceChunkAudio) {
@@ -16930,7 +17250,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16930
17250
  format: config.format
16931
17251
  };
16932
17252
  try {
16933
- rmSync13(workDir, { recursive: true, force: true });
17253
+ rmSync14(workDir, { recursive: true, force: true });
16934
17254
  } catch (err) {
16935
17255
  log.warn("[plan] failed to remove temp work dir", {
16936
17256
  workDir,
@@ -17501,14 +17821,14 @@ import {
17501
17821
  // src/server.ts
17502
17822
  import {
17503
17823
  existsSync as existsSync24,
17504
- mkdirSync as mkdirSync14,
17505
- statSync as statSync9,
17824
+ mkdirSync as mkdirSync15,
17825
+ statSync as statSync10,
17506
17826
  mkdtempSync as mkdtempSync5,
17507
- writeFileSync as writeFileSync9,
17508
- rmSync as rmSync14,
17827
+ writeFileSync as writeFileSync10,
17828
+ rmSync as rmSync15,
17509
17829
  createReadStream as createReadStream5
17510
17830
  } from "node:fs";
17511
- import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
17831
+ import { resolve as resolve11, dirname as dirname15, join as join38 } from "node:path";
17512
17832
  import { tmpdir as tmpdir3 } from "node:os";
17513
17833
  import { parseArgs } from "node:util";
17514
17834
  import crypto from "node:crypto";
@@ -17520,14 +17840,14 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
17520
17840
  // src/services/hyperframeLint.ts
17521
17841
  import {
17522
17842
  existsSync as existsSync22,
17523
- readFileSync as readFileSync12,
17524
- statSync as statSync8,
17525
- openSync as openSync6,
17843
+ readFileSync as readFileSync13,
17844
+ statSync as statSync9,
17845
+ openSync as openSync7,
17526
17846
  fstatSync as fstatSync3,
17527
- closeSync as closeSync6,
17847
+ closeSync as closeSync7,
17528
17848
  constants as constants3
17529
17849
  } from "node:fs";
17530
- import { resolve as resolve9, join as join35, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
17850
+ import { resolve as resolve10, join as join36, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
17531
17851
  import { lintHyperframeHtml } from "@hyperframes/lint";
17532
17852
  function isStringRecord(value) {
17533
17853
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -17557,30 +17877,30 @@ function pickEntryFile(files, preferredEntryFile) {
17557
17877
  function readEntryHtml(filePath) {
17558
17878
  let fd;
17559
17879
  try {
17560
- fd = openSync6(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
17880
+ fd = openSync7(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
17561
17881
  } catch (error) {
17562
17882
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
17563
17883
  return null;
17564
- if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
17884
+ if (!statSync9(filePath, { throwIfNoEntry: false })?.isFile()) return null;
17565
17885
  throw error;
17566
17886
  }
17567
17887
  try {
17568
17888
  if (!fstatSync3(fd).isFile()) return null;
17569
- return readFileSync12(fd, "utf-8");
17889
+ return readFileSync13(fd, "utf-8");
17570
17890
  } finally {
17571
- closeSync6(fd);
17891
+ closeSync7(fd);
17572
17892
  }
17573
17893
  }
17574
17894
  function readProjectEntryFile(projectDir, preferredEntryFile) {
17575
- const absProjectDir = resolve9(projectDir);
17576
- if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
17895
+ const absProjectDir = resolve10(projectDir);
17896
+ if (!existsSync22(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
17577
17897
  return { error: `Project directory not found: ${absProjectDir}` };
17578
17898
  }
17579
17899
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
17580
17900
  (value) => typeof value === "string" && value.trim().length > 0
17581
17901
  );
17582
17902
  for (const entryFile of entryCandidates) {
17583
- const absoluteEntryPath = resolve9(absProjectDir, entryFile);
17903
+ const absoluteEntryPath = resolve10(absProjectDir, entryFile);
17584
17904
  const entryRelativePath = relative6(absProjectDir, absoluteEntryPath);
17585
17905
  if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute4(entryRelativePath)) {
17586
17906
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
@@ -17595,7 +17915,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
17595
17915
  }
17596
17916
  }
17597
17917
  return {
17598
- error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
17918
+ error: `No HTML entry file found in project directory: ${join36(absProjectDir, preferredEntryFile || "index.html")}`
17599
17919
  };
17600
17920
  }
17601
17921
  function prepareHyperframeLintBody(body) {
@@ -17641,7 +17961,7 @@ async function runHyperframeLint(prepared) {
17641
17961
  // src/services/healthWorker.ts
17642
17962
  import { Worker as Worker2 } from "node:worker_threads";
17643
17963
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17644
- import { dirname as dirname13, join as join36 } from "node:path";
17964
+ import { dirname as dirname14, join as join37 } from "node:path";
17645
17965
  import { existsSync as existsSync23 } from "node:fs";
17646
17966
  var DEFAULT_HEALTH_PORT = 9848;
17647
17967
  async function startHealthWorker(options = {}) {
@@ -17660,12 +17980,12 @@ async function startHealthWorker(options = {}) {
17660
17980
  stdout: false,
17661
17981
  stderr: false
17662
17982
  });
17663
- await new Promise((resolve12, reject) => {
17983
+ await new Promise((resolve13, reject) => {
17664
17984
  const onMessage = (msg) => {
17665
17985
  if (msg?.type === "listening") {
17666
17986
  worker.off("error", onError);
17667
17987
  worker.off("message", onMessage);
17668
- resolve12();
17988
+ resolve13();
17669
17989
  } else if (msg?.type === "listen-error") {
17670
17990
  worker.off("error", onError);
17671
17991
  worker.off("message", onMessage);
@@ -17716,8 +18036,8 @@ function defaultLogger2() {
17716
18036
  };
17717
18037
  }
17718
18038
  function resolveWorkerEntry2() {
17719
- const here = dirname13(fileURLToPath6(import.meta.url));
17720
- const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
18039
+ const here = dirname14(fileURLToPath6(import.meta.url));
18040
+ const candidates = [join37(here, "healthWorkerThread.js"), join37(here, "healthWorkerThread.ts")];
17721
18041
  for (const candidate of candidates) {
17722
18042
  if (existsSync23(candidate)) return candidate;
17723
18043
  }
@@ -17735,6 +18055,8 @@ var DEFAULT_SERVER_FPS = { num: 30, den: 1 };
17735
18055
  var SSE_KEEPALIVE_MS = 3e4;
17736
18056
  var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
17737
18057
  "ASSET_MEDIA_TYPE_MISMATCH",
18058
+ "HEIF_IMAGE_CONVERSION_FAILED",
18059
+ "HEIF_TOOL_UNAVAILABLE",
17738
18060
  "NOT_MEDIA_PAYLOAD",
17739
18061
  "INVALID_VIDEO_METADATA",
17740
18062
  "VIDEO_SOURCE_UNRENDERABLE",
@@ -17870,8 +18192,8 @@ function buildRenderJobConfig(input, outputPath, log) {
17870
18192
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
17871
18193
  const { input, cleanupProjectDir } = prepared;
17872
18194
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
17873
- const outputDir = dirname14(absoluteOutputPath);
17874
- if (!existsSync24(outputDir)) mkdirSync14(outputDir, { recursive: true });
18195
+ const outputDir = dirname15(absoluteOutputPath);
18196
+ if (!existsSync24(outputDir)) mkdirSync15(outputDir, { recursive: true });
17875
18197
  return { input, cleanupProjectDir, absoluteOutputPath };
17876
18198
  }
17877
18199
  function validateRenderOverrides(body) {
@@ -17911,12 +18233,12 @@ var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
17911
18233
  function prepareProjectDirectory(projectDir, options) {
17912
18234
  const candidate = nonEmptyString(projectDir);
17913
18235
  if (!candidate) return null;
17914
- const absProjectDir = resolve10(candidate);
17915
- if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
18236
+ const absProjectDir = resolve11(candidate);
18237
+ if (!existsSync24(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
17916
18238
  return { error: `Project directory not found: ${absProjectDir}` };
17917
18239
  }
17918
18240
  const entry = options.entryFile || "index.html";
17919
- if (!existsSync24(resolve10(absProjectDir, entry))) {
18241
+ if (!existsSync24(resolve11(absProjectDir, entry))) {
17920
18242
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
17921
18243
  }
17922
18244
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -17937,8 +18259,8 @@ async function resolveInlineRenderHtml(body) {
17937
18259
  }
17938
18260
  function materializeInlineProject(html, options) {
17939
18261
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
17940
- const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
17941
- writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
18262
+ const tempProjectDir = mkdtempSync5(join38(tempRoot, "producer-project-"));
18263
+ writeFileSync10(join38(tempProjectDir, "index.html"), html, "utf-8");
17942
18264
  return {
17943
18265
  prepared: {
17944
18266
  input: { projectDir: tempProjectDir, ...options },
@@ -17963,7 +18285,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
17963
18285
  try {
17964
18286
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
17965
18287
  } catch (error) {
17966
- const fallbackPath = resolve10(rendersDir, `producer-fallback-${Date.now()}.mp4`);
18288
+ const fallbackPath = resolve11(rendersDir, `producer-fallback-${Date.now()}.mp4`);
17967
18289
  log.warn("Failed to resolve output path, using fallback", {
17968
18290
  fallback: fallbackPath,
17969
18291
  error: error instanceof Error ? error.message : String(error)
@@ -17999,7 +18321,7 @@ function createArtifactStore(ttlMs) {
17999
18321
  function cleanupTempDir(dir, log) {
18000
18322
  if (!dir) return;
18001
18323
  try {
18002
- rmSync14(dir, { recursive: true, force: true });
18324
+ rmSync15(dir, { recursive: true, force: true });
18003
18325
  } catch (error) {
18004
18326
  log.warn("Failed to cleanup temp project dir", {
18005
18327
  cleanupProjectDir: dir,
@@ -18008,7 +18330,7 @@ function cleanupTempDir(dir, log) {
18008
18330
  }
18009
18331
  }
18010
18332
  function outputFileSize(path) {
18011
- return existsSync24(path) ? statSync9(path).size : 0;
18333
+ return existsSync24(path) ? statSync10(path).size : 0;
18012
18334
  }
18013
18335
  function createBlockingProgressReporter(log, requestId) {
18014
18336
  let lastLoggedPct = -10;
@@ -18314,7 +18636,7 @@ function createRenderHandlers(options = {}) {
18314
18636
  store.delete(token);
18315
18637
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
18316
18638
  }
18317
- const stats = statSync9(artifact.path);
18639
+ const stats = statSync10(artifact.path);
18318
18640
  return new Response(createReadStream5(artifact.path), {
18319
18641
  headers: {
18320
18642
  "content-type": "video/mp4",
@@ -18381,7 +18703,7 @@ function startServer(options = {}) {
18381
18703
  process.on("SIGINT", () => shutdown("SIGINT"));
18382
18704
  return server;
18383
18705
  }
18384
- var entryScript = process.argv[1] ? resolve10(process.argv[1]) : "";
18706
+ var entryScript = process.argv[1] ? resolve11(process.argv[1]) : "";
18385
18707
  var isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
18386
18708
  if (isPublicServerEntry) {
18387
18709
  const { values } = parseArgs({
@@ -18406,35 +18728,35 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
18406
18728
 
18407
18729
  // src/services/distributed/planV2.ts
18408
18730
  import {
18409
- copyFileSync as copyFileSync7,
18731
+ copyFileSync as copyFileSync8,
18410
18732
  existsSync as existsSync26,
18411
18733
  lstatSync as lstatSync3,
18412
- mkdirSync as mkdirSync16,
18734
+ mkdirSync as mkdirSync17,
18413
18735
  mkdtempSync as mkdtempSync7,
18414
- readFileSync as readFileSync13,
18736
+ readFileSync as readFileSync14,
18415
18737
  readdirSync as readdirSync9,
18416
- rmSync as rmSync16,
18417
- statSync as statSync11,
18418
- writeFileSync as writeFileSync11
18738
+ rmSync as rmSync17,
18739
+ statSync as statSync12,
18740
+ writeFileSync as writeFileSync12
18419
18741
  } from "node:fs";
18420
18742
  import { tmpdir as tmpdir4 } from "node:os";
18421
- import { dirname as dirname16, isAbsolute as isAbsolute5, join as join40, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
18743
+ import { dirname as dirname17, isAbsolute as isAbsolute5, join as join41, relative as relative7, resolve as resolve12, sep as sep4 } from "node:path";
18422
18744
  import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
18423
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
18745
+ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
18424
18746
 
18425
18747
  // src/services/distributed/planV2Publisher.ts
18426
18748
  import {
18427
- copyFileSync as copyFileSync6,
18749
+ copyFileSync as copyFileSync7,
18428
18750
  existsSync as existsSync25,
18429
18751
  linkSync as linkSync2,
18430
- mkdirSync as mkdirSync15,
18752
+ mkdirSync as mkdirSync16,
18431
18753
  mkdtempSync as mkdtempSync6,
18432
- rmSync as rmSync15,
18433
- statSync as statSync10,
18434
- writeFileSync as writeFileSync10
18754
+ rmSync as rmSync16,
18755
+ statSync as statSync11,
18756
+ writeFileSync as writeFileSync11
18435
18757
  } from "node:fs";
18436
- import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
18437
- import { dirname as dirname15, join as join39 } from "node:path";
18758
+ import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
18759
+ import { dirname as dirname16, join as join40 } from "node:path";
18438
18760
 
18439
18761
  // src/services/distributed/planV2Errors.ts
18440
18762
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -18449,7 +18771,7 @@ var PlanV2IntegrityError = class extends Error {
18449
18771
  };
18450
18772
 
18451
18773
  // src/services/distributed/planV2Layout.ts
18452
- import { join as join38 } from "node:path";
18774
+ import { join as join39 } from "node:path";
18453
18775
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
18454
18776
  function assertPlanV2Sha256(value, field) {
18455
18777
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -18459,7 +18781,7 @@ function assertPlanV2Sha256(value, field) {
18459
18781
  }
18460
18782
  function planV2BlobPath(root, sha256) {
18461
18783
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
18462
- return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18784
+ return join39(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18463
18785
  }
18464
18786
 
18465
18787
  // src/services/distributed/planV2Publisher.ts
@@ -18499,12 +18821,12 @@ var LocalPlanV2ArtifactPublisher = class {
18499
18821
  }
18500
18822
  this.destinationDir = destinationDir;
18501
18823
  this.#linkFile = options.linkFile ?? linkSync2;
18502
- mkdirSync15(dirname15(destinationDir), { recursive: true });
18503
- this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
18824
+ mkdirSync16(dirname16(destinationDir), { recursive: true });
18825
+ this.temporaryDir = mkdtempSync6(join40(dirname16(destinationDir), ".plan-v2-publish-"));
18504
18826
  }
18505
18827
  async putBlob(blob) {
18506
18828
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
18507
- const sourceSize = statSync10(blob.sourcePath).size;
18829
+ const sourceSize = statSync11(blob.sourcePath).size;
18508
18830
  if (sourceSize !== blob.sizeBytes) {
18509
18831
  throw new PlanV2IntegrityError(
18510
18832
  `published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
@@ -18512,19 +18834,19 @@ var LocalPlanV2ArtifactPublisher = class {
18512
18834
  }
18513
18835
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18514
18836
  if (existsSync25(destinationPath)) return;
18515
- mkdirSync15(dirname15(destinationPath), { recursive: true });
18516
- const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18517
- const temporaryPath = join39(stagingDir, "blob");
18837
+ mkdirSync16(dirname16(destinationPath), { recursive: true });
18838
+ const stagingDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18839
+ const temporaryPath = join40(stagingDir, "blob");
18518
18840
  try {
18519
18841
  try {
18520
18842
  this.#linkFile(blob.sourcePath, temporaryPath);
18521
18843
  } catch (error) {
18522
18844
  if (!canFallbackToCopy(error)) throw error;
18523
- copyFileSync6(blob.sourcePath, temporaryPath);
18845
+ copyFileSync7(blob.sourcePath, temporaryPath);
18524
18846
  }
18525
- renameWithRetry7(temporaryPath, destinationPath);
18847
+ renameWithRetry8(temporaryPath, destinationPath);
18526
18848
  } finally {
18527
- rmSync15(stagingDir, { recursive: true, force: true });
18849
+ rmSync16(stagingDir, { recursive: true, force: true });
18528
18850
  }
18529
18851
  }
18530
18852
  async commitManifest(manifestBytes) {
@@ -18535,13 +18857,13 @@ var LocalPlanV2ArtifactPublisher = class {
18535
18857
  );
18536
18858
  }
18537
18859
  }
18538
- writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18539
- renameWithRetry7(this.temporaryDir, this.destinationDir);
18860
+ writeFileSync11(join40(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18861
+ renameWithRetry8(this.temporaryDir, this.destinationDir);
18540
18862
  this.#committed = true;
18541
18863
  }
18542
18864
  async abort() {
18543
18865
  if (!this.#committed) {
18544
- rmSync15(this.temporaryDir, { recursive: true, force: true });
18866
+ rmSync16(this.temporaryDir, { recursive: true, force: true });
18545
18867
  }
18546
18868
  }
18547
18869
  };
@@ -18560,7 +18882,7 @@ function isNonNegativeIntegerArray(value) {
18560
18882
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
18561
18883
  }
18562
18884
  function readJsonFile(path, label) {
18563
- const contents = readFileSync13(path, "utf-8");
18885
+ const contents = readFileSync14(path, "utf-8");
18564
18886
  try {
18565
18887
  const parsed = JSON.parse(contents);
18566
18888
  return parsed;
@@ -18577,10 +18899,10 @@ function assertSafeRelativePath(path) {
18577
18899
  }
18578
18900
  function listFiles(root) {
18579
18901
  const files = [];
18580
- const rootResolved = resolve11(root);
18902
+ const rootResolved = resolve12(root);
18581
18903
  function walk(dir) {
18582
18904
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
18583
- const absolutePath = join40(dir, entry.name);
18905
+ const absolutePath = join41(dir, entry.name);
18584
18906
  if (entry.isDirectory()) {
18585
18907
  walk(absolutePath);
18586
18908
  } else if (entry.isFile()) {
@@ -18607,13 +18929,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18607
18929
  }
18608
18930
  }
18609
18931
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18610
- const videoRoot = join40(executionPlanDir, "video-frames");
18932
+ const videoRoot = join41(executionPlanDir, "video-frames");
18611
18933
  if (!existsSync26(videoRoot)) return;
18612
18934
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18613
18935
  if (!videoEntry.isDirectory()) continue;
18614
- const videoDir = join40(videoRoot, videoEntry.name);
18936
+ const videoDir = join41(videoRoot, videoEntry.name);
18615
18937
  if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
18616
- const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18938
+ const sentinelPath = join41(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18617
18939
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
18618
18940
  }
18619
18941
  }
@@ -18623,8 +18945,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
18623
18945
  return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
18624
18946
  }
18625
18947
  function resolveExtractedVideoOutputDir(planDir, videoId) {
18626
- const videoRoot = resolve11(planDir, "video-frames");
18627
- const outputDir = resolve11(videoRoot, videoId);
18948
+ const videoRoot = resolve12(planDir, "video-frames");
18949
+ const outputDir = resolve12(videoRoot, videoId);
18628
18950
  if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
18629
18951
  throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
18630
18952
  }
@@ -18660,7 +18982,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18660
18982
  if (video.deferredRange) {
18661
18983
  const framePaths2 = /* @__PURE__ */ new Map();
18662
18984
  for (let index = 0; index < video.totalFrames; index++) {
18663
- framePaths2.set(index, join40(outputDir, String(index)));
18985
+ framePaths2.set(index, join41(outputDir, String(index)));
18664
18986
  }
18665
18987
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
18666
18988
  }
@@ -18668,7 +18990,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18668
18990
  const framePaths = /* @__PURE__ */ new Map();
18669
18991
  for (const frameName of frameNames) {
18670
18992
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
18671
- assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
18993
+ assertValidExtractionCacheCompleteSentinel(join41(outputDir, frameName));
18672
18994
  continue;
18673
18995
  }
18674
18996
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -18687,7 +19009,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18687
19009
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
18688
19010
  );
18689
19011
  }
18690
- framePaths.set(frameIndex, join40(outputDir, frameName));
19012
+ framePaths.set(frameIndex, join41(outputDir, frameName));
18691
19013
  }
18692
19014
  return {
18693
19015
  ...video,
@@ -18708,11 +19030,11 @@ function parsePlanVideosJson2(value) {
18708
19030
  }
18709
19031
  }
18710
19032
  function materializeExtractedVideoDirectories(planDir) {
18711
- const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19033
+ const videosPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18712
19034
  if (!existsSync26(videosPath)) return;
18713
19035
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18714
19036
  for (const video of videos.extracted) {
18715
- mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
19037
+ mkdirSync17(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
18716
19038
  }
18717
19039
  }
18718
19040
  function parseChunkSlices(value) {
@@ -18742,13 +19064,13 @@ function parseChunkSlices(value) {
18742
19064
  });
18743
19065
  }
18744
19066
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18745
- const videoRoot = join40(executionPlanDir, "video-frames");
19067
+ const videoRoot = join41(executionPlanDir, "video-frames");
18746
19068
  const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
18747
- const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19069
+ const videosPath = join41(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18748
19070
  if (!existsSync26(videosPath)) {
18749
19071
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18750
19072
  }
18751
- const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
19073
+ const chunksPath = join41(executionPlanDir, "meta", "chunks.json");
18752
19074
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
18753
19075
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
18754
19076
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -18785,7 +19107,7 @@ function collectChunkVideoDependencies(input) {
18785
19107
  for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
18786
19108
  const globalTime = frame * input.fpsDen / input.fpsNum;
18787
19109
  for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
18788
- const path = input.deferredSources.get(videoId) ?? relative7(resolve11(input.executionPlanDir), payload.framePath).split(sep4).join("/");
19110
+ const path = input.deferredSources.get(videoId) ?? relative7(resolve12(input.executionPlanDir), payload.framePath).split(sep4).join("/");
18789
19111
  assertSafeRelativePath(path);
18790
19112
  const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
18791
19113
  indexes.add(chunk.index);
@@ -18803,21 +19125,21 @@ function computeManifestHash(manifest) {
18803
19125
  }
18804
19126
  function writeBlob(sourcePath, destinationPath) {
18805
19127
  if (existsSync26(destinationPath)) return;
18806
- mkdirSync16(dirname16(destinationPath), { recursive: true });
18807
- const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18808
- const temporaryPath = join40(temporaryDir, "blob");
19128
+ mkdirSync17(dirname17(destinationPath), { recursive: true });
19129
+ const temporaryDir = mkdtempSync7(join41(dirname17(destinationPath), ".plan-v2-blob-"));
19130
+ const temporaryPath = join41(temporaryDir, "blob");
18809
19131
  try {
18810
- copyFileSync7(sourcePath, temporaryPath);
18811
- renameWithRetry8(temporaryPath, destinationPath);
19132
+ copyFileSync8(sourcePath, temporaryPath);
19133
+ renameWithRetry9(temporaryPath, destinationPath);
18812
19134
  } finally {
18813
- rmSync16(temporaryDir, { recursive: true, force: true });
19135
+ rmSync17(temporaryDir, { recursive: true, force: true });
18814
19136
  }
18815
19137
  }
18816
19138
  function publishBlobFor(absolutePath, digests) {
18817
19139
  return {
18818
19140
  sourcePath: absolutePath,
18819
19141
  sha256: planFileSha256(absolutePath, digests),
18820
- sizeBytes: statSync11(absolutePath).size
19142
+ sizeBytes: statSync12(absolutePath).size
18821
19143
  };
18822
19144
  }
18823
19145
  async function putBlobs(publisher, blobs) {
@@ -18841,7 +19163,7 @@ async function putTasks(tasks) {
18841
19163
  if (failure) throw failure.reason;
18842
19164
  }
18843
19165
  function buildPlanV2Publication(executionPlanDir, digests) {
18844
- const executionPlanPath = join40(executionPlanDir, "plan.json");
19166
+ const executionPlanPath = join41(executionPlanDir, "plan.json");
18845
19167
  if (!existsSync26(executionPlanPath)) {
18846
19168
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18847
19169
  }
@@ -18869,7 +19191,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18869
19191
  }
18870
19192
  validateExtractionCacheCompleteSentinels(executionPlanDir);
18871
19193
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
18872
- const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
19194
+ const encoder = readJsonFile(join41(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18873
19195
  const files = listFiles(executionPlanDir);
18874
19196
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
18875
19197
  for (const file of files) {
@@ -18929,21 +19251,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18929
19251
  if (publication.storedBlobs.length > 0) {
18930
19252
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
18931
19253
  }
18932
- mkdirSync16(dirname16(planV2Dir), { recursive: true });
18933
- const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
19254
+ mkdirSync17(dirname17(planV2Dir), { recursive: true });
19255
+ const tempDir = mkdtempSync7(join41(dirname17(planV2Dir), ".plan-v2-build-"));
18934
19256
  try {
18935
19257
  for (const blob of publication.blobs) {
18936
19258
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
18937
19259
  }
18938
- writeFileSync11(
18939
- join40(tempDir, "plan.json"),
19260
+ writeFileSync12(
19261
+ join41(tempDir, "plan.json"),
18940
19262
  canonicalJsonStringify(publication.manifest),
18941
19263
  "utf-8"
18942
19264
  );
18943
- renameWithRetry8(tempDir, planV2Dir);
19265
+ renameWithRetry9(tempDir, planV2Dir);
18944
19266
  return resultFromManifest(planV2Dir, publication.manifest);
18945
19267
  } catch (error) {
18946
- rmSync16(tempDir, { recursive: true, force: true });
19268
+ rmSync17(tempDir, { recursive: true, force: true });
18947
19269
  throw error;
18948
19270
  }
18949
19271
  }
@@ -18983,7 +19305,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
18983
19305
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
18984
19306
  }
18985
19307
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
18986
- const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
19308
+ const stagingRoot = mkdtempSync7(join41(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
18987
19309
  const digests = /* @__PURE__ */ new Map();
18988
19310
  try {
18989
19311
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -18994,7 +19316,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18994
19316
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
18995
19317
  whileAudioMixes: () => putBlobs(
18996
19318
  publisher,
18997
- listFiles(join40(stagingRoot, "compiled")).map(
19319
+ listFiles(join41(stagingRoot, "compiled")).map(
18998
19320
  (file) => publishBlobFor(file.absolutePath, digests)
18999
19321
  )
19000
19322
  ),
@@ -19010,7 +19332,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
19010
19332
  }
19011
19333
  throw error;
19012
19334
  } finally {
19013
- rmSync16(stagingRoot, { recursive: true, force: true });
19335
+ rmSync17(stagingRoot, { recursive: true, force: true });
19014
19336
  }
19015
19337
  }
19016
19338
  async function planV2(projectDir, config, planV2Dir) {
@@ -19019,14 +19341,14 @@ async function planV2(projectDir, config, planV2Dir) {
19019
19341
  }
19020
19342
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
19021
19343
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
19022
- stagingParentDir: dirname16(planV2Dir)
19344
+ stagingParentDir: dirname17(planV2Dir)
19023
19345
  });
19024
19346
  return resultFromManifest(planV2Dir, manifest);
19025
19347
  }
19026
19348
  function resultFromManifest(planV2Dir, manifest) {
19027
19349
  return {
19028
19350
  planDir: planV2Dir,
19029
- manifestPath: join40(planV2Dir, "plan.json"),
19351
+ manifestPath: join41(planV2Dir, "plan.json"),
19030
19352
  planProtocol: PLAN_PROTOCOL_V2,
19031
19353
  planHash: manifest.planHash,
19032
19354
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -19162,7 +19484,7 @@ function parsePlanV2Manifest(value) {
19162
19484
  return parsed;
19163
19485
  }
19164
19486
  function readPlanV2Manifest(planV2Dir) {
19165
- const manifestPath = join40(planV2Dir, "plan.json");
19487
+ const manifestPath = join41(planV2Dir, "plan.json");
19166
19488
  if (!existsSync26(manifestPath)) {
19167
19489
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
19168
19490
  }
@@ -19185,7 +19507,7 @@ function verifyBlob(planV2Dir, artifact) {
19185
19507
  if (!existsSync26(sourcePath)) {
19186
19508
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
19187
19509
  }
19188
- const stats = statSync11(sourcePath);
19510
+ const stats = statSync12(sourcePath);
19189
19511
  if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
19190
19512
  throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
19191
19513
  }
@@ -19207,17 +19529,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19207
19529
  artifact,
19208
19530
  sourcePath: verifyBlob(planV2Dir, artifact)
19209
19531
  }));
19210
- mkdirSync16(dirname16(destinationDir), { recursive: true });
19211
- const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
19532
+ mkdirSync17(dirname17(destinationDir), { recursive: true });
19533
+ const tempDir = mkdtempSync7(join41(dirname17(destinationDir), ".plan-v2-materialize-"));
19212
19534
  try {
19213
19535
  for (const { artifact, sourcePath } of verified) {
19214
- const destinationPath = join40(tempDir, ...artifact.path.split("/"));
19215
- mkdirSync16(dirname16(destinationPath), { recursive: true });
19216
- copyFileSync7(sourcePath, destinationPath);
19536
+ const destinationPath = join41(tempDir, ...artifact.path.split("/"));
19537
+ mkdirSync17(dirname17(destinationPath), { recursive: true });
19538
+ copyFileSync8(sourcePath, destinationPath);
19217
19539
  }
19218
19540
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
19219
- writeFileSync11(
19220
- join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19541
+ writeFileSync12(
19542
+ join41(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19221
19543
  canonicalJsonStringify({
19222
19544
  manifest,
19223
19545
  target,
@@ -19225,9 +19547,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19225
19547
  }),
19226
19548
  "utf-8"
19227
19549
  );
19228
- renameWithRetry8(tempDir, destinationDir);
19550
+ renameWithRetry9(tempDir, destinationDir);
19229
19551
  } catch (error) {
19230
- rmSync16(tempDir, { recursive: true, force: true });
19552
+ rmSync17(tempDir, { recursive: true, force: true });
19231
19553
  throw error;
19232
19554
  }
19233
19555
  return {
@@ -19240,7 +19562,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19240
19562
  // Join the artifact's OWN name, not the current constant: a plan written
19241
19563
  // before the container change carries the legacy name, and materializing it
19242
19564
  // under the new one would point at a file that was never written.
19243
- audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
19565
+ audioPath: target.role === "assembler" ? audioArtifact ? join41(destinationDir, audioArtifact.path) : null : null
19244
19566
  };
19245
19567
  }
19246
19568
  function assertRemoteSources(remoteSources, artifacts) {
@@ -19259,7 +19581,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
19259
19581
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
19260
19582
  }
19261
19583
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19262
- const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19584
+ const markerPath = join41(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19263
19585
  if (!existsSync26(markerPath)) return null;
19264
19586
  const marker = readJsonFile(markerPath, "materialization marker");
19265
19587
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -19277,8 +19599,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19277
19599
  assertRemoteSources(remoteSources, required);
19278
19600
  for (const artifact of required) {
19279
19601
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
19280
- const path = join40(planDir, ...artifact.path.split("/"));
19281
- if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
19602
+ const path = join41(planDir, ...artifact.path.split("/"));
19603
+ if (!existsSync26(path) || statSync12(path).size !== artifact.sizeBytes) {
19282
19604
  throw new PlanV2IntegrityError(
19283
19605
  `materialized artifact missing or truncated: ${artifact.path}`
19284
19606
  );
@@ -19291,23 +19613,23 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19291
19613
  }
19292
19614
 
19293
19615
  // src/services/distributed/planV2Execution.ts
19294
- import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
19616
+ import { mkdtempSync as mkdtempSync9, rmSync as rmSync20 } from "node:fs";
19295
19617
  import { tmpdir as tmpdir5 } from "node:os";
19296
- import { join as join43 } from "node:path";
19618
+ import { join as join44 } from "node:path";
19297
19619
 
19298
19620
  // src/services/distributed/assemble.ts
19299
19621
  import {
19300
19622
  cpSync as cpSync3,
19301
19623
  existsSync as existsSync27,
19302
- mkdirSync as mkdirSync17,
19624
+ mkdirSync as mkdirSync18,
19303
19625
  mkdtempSync as mkdtempSync8,
19304
- readFileSync as readFileSync14,
19626
+ readFileSync as readFileSync15,
19305
19627
  readdirSync as readdirSync10,
19306
- rmSync as rmSync17,
19307
- statSync as statSync12,
19308
- writeFileSync as writeFileSync12
19628
+ rmSync as rmSync18,
19629
+ statSync as statSync13,
19630
+ writeFileSync as writeFileSync13
19309
19631
  } from "node:fs";
19310
- import { dirname as dirname17, join as join41 } from "node:path";
19632
+ import { dirname as dirname18, join as join42 } from "node:path";
19311
19633
  import {
19312
19634
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
19313
19635
  describeFfmpegFailure as describeFfmpegFailure4,
@@ -19315,9 +19637,9 @@ import {
19315
19637
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
19316
19638
  muxVideoWithAudio as muxVideoWithAudio2,
19317
19639
  resolveConfig as resolveConfig6,
19318
- runFfmpeg as runFfmpeg7,
19640
+ runFfmpeg as runFfmpeg8,
19319
19641
  appendSoftwareEncoderThreadsArg,
19320
- renameWithRetry as renameWithRetry9
19642
+ renameWithRetry as renameWithRetry10
19321
19643
  } from "@hyperframes/engine";
19322
19644
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
19323
19645
  function cfrRateControlArgs(locked) {
@@ -19343,13 +19665,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19343
19665
  outputPath,
19344
19666
  durationMs: Date.now() - start,
19345
19667
  framesEncoded: plan2.totalFrames,
19346
- fileSize: statSync12(outputPath).size
19668
+ fileSize: statSync13(outputPath).size
19347
19669
  };
19348
19670
  }
19349
19671
  const format = plan2.dimensions.format;
19350
19672
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
19351
- if (!existsSync27(dirname17(outputPath))) {
19352
- mkdirSync17(dirname17(outputPath), { recursive: true });
19673
+ if (!existsSync27(dirname18(outputPath))) {
19674
+ mkdirSync18(dirname18(outputPath), { recursive: true });
19353
19675
  }
19354
19676
  const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
19355
19677
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -19371,10 +19693,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19371
19693
  };
19372
19694
  try {
19373
19695
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
19374
- const finalPath = join41(workDir, `output.${format}`);
19696
+ const finalPath = join42(workDir, `output.${format}`);
19375
19697
  const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
19376
19698
  await writeJoined(ctx, videoSources, audio, finalPath);
19377
- renameWithRetry9(finalPath, outputPath);
19699
+ renameWithRetry10(finalPath, outputPath);
19378
19700
  log.info("[assemble] stage timings", {
19379
19701
  ...stageMs,
19380
19702
  chunks: chunkPaths.length,
@@ -19382,7 +19704,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19382
19704
  });
19383
19705
  } finally {
19384
19706
  try {
19385
- rmSync17(workDir, { recursive: true, force: true });
19707
+ rmSync18(workDir, { recursive: true, force: true });
19386
19708
  } catch (err) {
19387
19709
  log.warn("[assemble] failed to remove work dir", {
19388
19710
  workDir,
@@ -19390,7 +19712,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19390
19712
  });
19391
19713
  }
19392
19714
  }
19393
- const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
19715
+ const fileSize = existsSync27(outputPath) ? statSync13(outputPath).size : 0;
19394
19716
  return {
19395
19717
  outputPath,
19396
19718
  durationMs: Date.now() - start,
@@ -19399,18 +19721,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19399
19721
  };
19400
19722
  }
19401
19723
  function readAssemblePlan(planDir, chunkPaths) {
19402
- const planJsonPath = join41(planDir, "plan.json");
19403
- const chunksJsonPath = join41(planDir, "meta", "chunks.json");
19724
+ const planJsonPath = join42(planDir, "plan.json");
19725
+ const chunksJsonPath = join42(planDir, "meta", "chunks.json");
19404
19726
  if (!existsSync27(planJsonPath)) {
19405
19727
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
19406
19728
  }
19407
- const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
19729
+ const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
19408
19730
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
19409
19731
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
19410
19732
  if (!existsSync27(chunksJsonPath)) {
19411
19733
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
19412
19734
  }
19413
- const chunks = JSON.parse(readFileSync14(chunksJsonPath, "utf-8"));
19735
+ const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
19414
19736
  if (chunkPaths.length !== chunks.length) {
19415
19737
  throw new Error(
19416
19738
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -19448,7 +19770,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
19448
19770
  return args;
19449
19771
  }
19450
19772
  async function reencodeCfr(ctx, chunkPaths, encoder) {
19451
- const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
19773
+ const cfrOutputPath = join42(ctx.workDir, `cfr.${ctx.format}`);
19452
19774
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
19453
19775
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
19454
19776
  cfrArgs.push("-y", cfrOutputPath);
@@ -19475,7 +19797,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19475
19797
  );
19476
19798
  return;
19477
19799
  }
19478
- const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
19800
+ const paddedAudioPath = join42(ctx.workDir, "audio-padded.m4a");
19479
19801
  const padTrimResult = await ctx.timed(
19480
19802
  "audioMs",
19481
19803
  () => padOrTrimAudioToVideoFrameCount({
@@ -19515,9 +19837,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19515
19837
  }
19516
19838
  }
19517
19839
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
19518
- const listPath = join41(workDir, "concat-list.txt");
19840
+ const listPath = join42(workDir, "concat-list.txt");
19519
19841
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
19520
- writeFileSync12(listPath, `${body}
19842
+ writeFileSync13(listPath, `${body}
19521
19843
  `, "utf-8");
19522
19844
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
19523
19845
  }
@@ -19532,7 +19854,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
19532
19854
  };
19533
19855
  }
19534
19856
  async function runAssembleFfmpeg(args, step, signal, timeout) {
19535
- const result = await runFfmpeg7(args, { signal, timeout });
19857
+ const result = await runFfmpeg8(args, { signal, timeout });
19536
19858
  if (!result.success) {
19537
19859
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
19538
19860
  error: describeFfmpegFailure4(result, timeout),
@@ -19553,13 +19875,13 @@ function stitchesSeparateAudio(planDir) {
19553
19875
  return lockedAudio(planDir).separateAudio === true;
19554
19876
  }
19555
19877
  function planFormat(planDir) {
19556
- const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
19878
+ const plan2 = JSON.parse(readFileSync15(join42(planDir, "plan.json"), "utf-8"));
19557
19879
  return plan2.dimensions.format;
19558
19880
  }
19559
19881
  function lockedAudio(planDir) {
19560
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19882
+ const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19561
19883
  if (!existsSync27(encoderJsonPath)) return {};
19562
- return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
19884
+ return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19563
19885
  }
19564
19886
  function readCfrEncoder(planDir, format) {
19565
19887
  if (format !== "mp4") {
@@ -19567,11 +19889,11 @@ function readCfrEncoder(planDir, format) {
19567
19889
  `[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
19568
19890
  );
19569
19891
  }
19570
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19892
+ const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19571
19893
  if (!existsSync27(encoderJsonPath)) {
19572
19894
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
19573
19895
  }
19574
- const encoderJson = JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
19896
+ const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19575
19897
  if (encoderJson.encoder === "libx265-software") {
19576
19898
  throw new Error(
19577
19899
  `[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
@@ -19580,11 +19902,11 @@ function readCfrEncoder(planDir, format) {
19580
19902
  return encoderJson;
19581
19903
  }
19582
19904
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
19583
- if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
19584
- mkdirSync17(outputPath, { recursive: true });
19905
+ if (existsSync27(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
19906
+ mkdirSync18(outputPath, { recursive: true });
19585
19907
  let globalIdx = 0;
19586
19908
  for (const chunkDir of chunkPaths) {
19587
- if (!statSync12(chunkDir).isDirectory()) {
19909
+ if (!statSync13(chunkDir).isDirectory()) {
19588
19910
  throw new Error(
19589
19911
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
19590
19912
  );
@@ -19594,8 +19916,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19594
19916
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
19595
19917
  }
19596
19918
  for (const frame of frames) {
19597
- const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
19598
- cpSync3(join41(chunkDir, frame), dst);
19919
+ const dst = join42(outputPath, formatExportFrameName(globalIdx, "png"));
19920
+ cpSync3(join42(chunkDir, frame), dst);
19599
19921
  globalIdx += 1;
19600
19922
  }
19601
19923
  }
@@ -19605,13 +19927,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19605
19927
  );
19606
19928
  }
19607
19929
  if (audioPath !== null && existsSync27(audioPath)) {
19608
- const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
19930
+ const sidecar = join42(outputPath, MIXED_AUDIO_FILENAME4);
19609
19931
  cpSync3(audioPath, sidecar);
19610
19932
  }
19611
19933
  let fileSize = 0;
19612
19934
  for (const name of readdirSync10(outputPath)) {
19613
19935
  try {
19614
- fileSize += statSync12(join41(outputPath, name)).size;
19936
+ fileSize += statSync13(join42(outputPath, name)).size;
19615
19937
  } catch {
19616
19938
  }
19617
19939
  }
@@ -19625,9 +19947,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19625
19947
 
19626
19948
  // src/services/distributed/renderChunk.ts
19627
19949
  import { randomBytes } from "node:crypto";
19628
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync18, writeFileSync as writeFileSync13 } from "node:fs";
19950
+ import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as rmSync19, writeFileSync as writeFileSync14 } from "node:fs";
19629
19951
  import { cpus as cpus3 } from "node:os";
19630
- import { join as join42 } from "node:path";
19952
+ import { join as join43 } from "node:path";
19631
19953
  import {
19632
19954
  assertSwiftShader,
19633
19955
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -19746,10 +20068,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
19746
20068
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
19747
20069
  }
19748
20070
  function hashChunkOutput(outputPath, kind) {
19749
- if (kind === "file") return sha256Hex(readFileSync15(outputPath));
20071
+ if (kind === "file") return sha256Hex(readFileSync16(outputPath));
19750
20072
  const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
19751
20073
  const lines = entries.map(
19752
- (name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
20074
+ (name) => `${name}\0${sha256Hex(readFileSync16(join43(outputPath, name)))}`
19753
20075
  );
19754
20076
  return sha256Hex(lines.join("\0"));
19755
20077
  }
@@ -19766,16 +20088,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
19766
20088
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19767
20089
  const start = Date.now();
19768
20090
  const log = defaultLogger;
19769
- const planJsonPath = join42(planDir, "plan.json");
19770
- const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19771
- const chunksJsonPath = join42(planDir, "meta", "chunks.json");
20091
+ const planJsonPath = join43(planDir, "plan.json");
20092
+ const encoderJsonPath = join43(planDir, "meta", "encoder.json");
20093
+ const chunksJsonPath = join43(planDir, "meta", "chunks.json");
19772
20094
  if (!existsSync28(planJsonPath)) {
19773
20095
  throw new RenderChunkValidationError(
19774
20096
  MISSING_PLAN_ARTIFACT,
19775
20097
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
19776
20098
  );
19777
20099
  }
19778
- const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
20100
+ const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
19779
20101
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
19780
20102
  const planHashStarted = Date.now();
19781
20103
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -19792,13 +20114,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19792
20114
  );
19793
20115
  }
19794
20116
  }
19795
- const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19796
- const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
19797
- const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
20117
+ const encoder = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
20118
+ const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
20119
+ const videosJsonPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19798
20120
  let planVideos = null;
19799
20121
  if (existsSync28(videosJsonPath)) {
19800
20122
  try {
19801
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync15(videosJsonPath, "utf-8")));
20123
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync16(videosJsonPath, "utf-8")));
19802
20124
  } catch (err) {
19803
20125
  if (err instanceof RenderChunkValidationError) throw err;
19804
20126
  throw new RenderChunkValidationError(
@@ -19826,7 +20148,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19826
20148
  }
19827
20149
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19828
20150
  const framesInChunk = range.endFrame - range.startFrame;
19829
- const compiledDir = join42(planDir, "compiled");
20151
+ const compiledDir = join43(planDir, "compiled");
19830
20152
  if (!existsSync28(compiledDir)) {
19831
20153
  throw new RenderChunkValidationError(
19832
20154
  MISSING_PLAN_ARTIFACT,
@@ -19883,9 +20205,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19883
20205
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
19884
20206
  };
19885
20207
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
19886
- mkdirSync18(workDir, { recursive: true });
19887
- const framesDir = join42(workDir, "captured-frames");
19888
- mkdirSync18(framesDir, { recursive: true });
20208
+ mkdirSync19(workDir, { recursive: true });
20209
+ const framesDir = join43(workDir, "captured-frames");
20210
+ mkdirSync19(framesDir, { recursive: true });
19889
20211
  const videoFramesDir = `${workDir}.video-frames`;
19890
20212
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
19891
20213
  const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -19909,7 +20231,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19909
20231
  dimensions: plan2.dimensions,
19910
20232
  encoder,
19911
20233
  videoCount: planVideos?.videos.length ?? 0,
19912
- html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
20234
+ html: readFileSync16(join43(compiledDir, "index.html"), "utf-8"),
19913
20235
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19914
20236
  skipReadinessVideoIds: deferredVideos.filter(
19915
20237
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -20038,10 +20360,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20038
20360
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
20039
20361
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
20040
20362
  if (isPngSequence) {
20041
- if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
20363
+ if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
20042
20364
  } else {
20043
- const outDir = join42(outputChunkPath, "..");
20044
- if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
20365
+ const outDir = join43(outputChunkPath, "..");
20366
+ if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
20045
20367
  }
20046
20368
  const encodeInput = {
20047
20369
  job,
@@ -20152,8 +20474,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20152
20474
  return observedChunkCaptureMode(capturePerfs);
20153
20475
  },
20154
20476
  resetForScreenshotRetry: () => {
20155
- rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20156
- mkdirSync18(framesDir, { recursive: true });
20477
+ rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20478
+ mkdirSync19(framesDir, { recursive: true });
20157
20479
  capturePerfs.length = 0;
20158
20480
  captureAttempts.length = 0;
20159
20481
  job.framesRendered = 0;
@@ -20195,8 +20517,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20195
20517
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
20196
20518
  timeoutMs: cfg.ffmpegProcessTimeout
20197
20519
  });
20198
- rmSync18(videoOnlyPath, { force: true });
20199
- rmSync18(audioScratchPath, { force: true });
20520
+ rmSync19(videoOnlyPath, { force: true });
20521
+ rmSync19(audioScratchPath, { force: true });
20200
20522
  }
20201
20523
  encodeStageMs = Date.now() - encodeStarted;
20202
20524
  } finally {
@@ -20243,11 +20565,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20243
20565
  producerVersion: plan2.producerVersion,
20244
20566
  ffmpegVersion
20245
20567
  };
20246
- writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
20568
+ writeFileSync14(perfPath, `${JSON.stringify(perfPayload, null, 2)}
20247
20569
  `, "utf-8");
20248
20570
  try {
20249
- rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20250
- rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20571
+ rmSync19(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20572
+ rmSync19(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20251
20573
  } catch (err) {
20252
20574
  log.warn("[renderChunk] failed to remove work dir", {
20253
20575
  workDir,
@@ -20276,18 +20598,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20276
20598
 
20277
20599
  // src/services/distributed/planV2Execution.ts
20278
20600
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
20279
- const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
20280
- const materializedPlanDir = join43(workRoot, "plan");
20601
+ const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-chunk-"));
20602
+ const materializedPlanDir = join44(workRoot, "plan");
20281
20603
  try {
20282
20604
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
20283
20605
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
20284
20606
  } finally {
20285
- rmSync19(workRoot, { recursive: true, force: true });
20607
+ rmSync20(workRoot, { recursive: true, force: true });
20286
20608
  }
20287
20609
  }
20288
20610
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20289
- const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
20290
- const materializedPlanDir = join43(workRoot, "plan");
20611
+ const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-assembler-"));
20612
+ const materializedPlanDir = join44(workRoot, "plan");
20291
20613
  try {
20292
20614
  const materialized = materializePlanV2Target(
20293
20615
  planV2Dir,
@@ -20302,7 +20624,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20302
20624
  options
20303
20625
  );
20304
20626
  } finally {
20305
- rmSync19(workRoot, { recursive: true, force: true });
20627
+ rmSync20(workRoot, { recursive: true, force: true });
20306
20628
  }
20307
20629
  }
20308
20630
 
@@ -20310,7 +20632,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20310
20632
  var READ_CHUNK_BYTES = 256 * 1024;
20311
20633
 
20312
20634
  // src/services/distributed/audioSidecar.ts
20313
- import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
20635
+ import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
20314
20636
  var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
20315
20637
 
20316
20638
  // src/services/distributed/probeFacts.ts
@@ -20374,6 +20696,7 @@ export {
20374
20696
  localizeRemoteImageSources,
20375
20697
  localizeRemoteMediaSources,
20376
20698
  materializePlanV2Target,
20699
+ mediaProfileMatchesElementType,
20377
20700
  normalizeErrorMessage,
20378
20701
  parseRenderRequest,
20379
20702
  plan,
@@ -20381,6 +20704,7 @@ export {
20381
20704
  planV2WithPublisher,
20382
20705
  prepareAnimatedGifInputs,
20383
20706
  prepareCaptureSessionForReuse4 as prepareCaptureSessionForReuse,
20707
+ prepareHeifImageInputs,
20384
20708
  prepareHyperframeLintBody,
20385
20709
  publishPlanV2FromExecutionPlan,
20386
20710
  publishPlanV2FromV1,