@hyperframes/producer 0.8.205 → 0.8.206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,14 @@
3
3
  // src/server.ts
4
4
  import {
5
5
  existsSync as existsSync19,
6
- mkdirSync as mkdirSync10,
7
- statSync as statSync9,
6
+ mkdirSync as mkdirSync11,
7
+ statSync as statSync10,
8
8
  mkdtempSync as mkdtempSync4,
9
- writeFileSync as writeFileSync6,
10
- rmSync as rmSync12,
9
+ writeFileSync as writeFileSync7,
10
+ rmSync as rmSync13,
11
11
  createReadStream as createReadStream3
12
12
  } from "node:fs";
13
- import { resolve as resolve9, dirname as dirname12, join as join30 } from "node:path";
13
+ import { resolve as resolve10, dirname as dirname13, join as join31 } from "node:path";
14
14
  import { tmpdir as tmpdir3 } from "node:os";
15
15
  import { parseArgs } from "node:util";
16
16
  import crypto from "node:crypto";
@@ -23,13 +23,13 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
23
23
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
24
24
  import {
25
25
  existsSync as existsSync16,
26
- mkdirSync as mkdirSync9,
27
- readFileSync as readFileSync8,
26
+ mkdirSync as mkdirSync10,
27
+ readFileSync as readFileSync9,
28
28
  readdirSync as readdirSync5,
29
- rmSync as rmSync11,
30
- statSync as statSync7,
31
- writeFileSync as writeFileSync5,
32
- copyFileSync as copyFileSync4,
29
+ rmSync as rmSync12,
30
+ statSync as statSync8,
31
+ writeFileSync as writeFileSync6,
32
+ copyFileSync as copyFileSync5,
33
33
  appendFileSync
34
34
  } from "fs";
35
35
  import { tmpdir as tmpdir2 } from "node:os";
@@ -77,7 +77,7 @@ import {
77
77
  augmentProtocolTimeoutError,
78
78
  augmentPageNavigationTimeoutError
79
79
  } from "@hyperframes/engine";
80
- import { join as join26, dirname as dirname10, resolve as resolve7 } from "path";
80
+ import { join as join27, dirname as dirname11, resolve as resolve8 } from "path";
81
81
  import { totalmem as totalmem2 } from "node:os";
82
82
  import { randomUUID } from "crypto";
83
83
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -101,6 +101,7 @@ import { Readable } from "node:stream";
101
101
  import { join, extname, resolve as resolve2, sep } from "node:path";
102
102
  import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
103
103
  import { fpsToNumber } from "@hyperframes/core";
104
+ import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
104
105
 
105
106
  // src/services/hyperframeRuntimeLoader.ts
106
107
  import { createHash } from "node:crypto";
@@ -217,11 +218,11 @@ var defaultLogger = createConsoleLogger("info");
217
218
 
218
219
  // src/services/fileServer.ts
219
220
  var readFileAsync = promisify(readFile);
220
- function isPathInside(child, parent, options = {}) {
221
+ function isPathInside(child2, parent, options = {}) {
221
222
  const { resolveSymlinks = false, pathModule } = options;
222
223
  const resolveFn = pathModule?.resolve ?? resolve2;
223
224
  const separator = pathModule?.sep ?? sep;
224
- const resolvedChild = resolveFn(child);
225
+ const resolvedChild = resolveFn(child2);
225
226
  const resolvedParent = resolveFn(parent);
226
227
  const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
227
228
  const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
@@ -741,6 +742,18 @@ function createFileServer(options) {
741
742
  html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
742
743
  return c.text(html, 200, { "Content-Type": contentType });
743
744
  }
745
+ if (ext === ".png") {
746
+ const untagged = stripSdrPngColourTags(await readFileAsync(fd));
747
+ if (untagged) {
748
+ const headers = {
749
+ "Content-Type": contentType,
750
+ "Content-Length": String(untagged.length)
751
+ };
752
+ return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
753
+ headers
754
+ });
755
+ }
756
+ }
744
757
  const totalSize = stat.size;
745
758
  const rangeHeader = c.req.header("range");
746
759
  const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
@@ -768,7 +781,8 @@ function createFileServer(options) {
768
781
  const stream = createReadStream(filePath, {
769
782
  fd,
770
783
  autoClose: true,
771
- ...range ? { start: range.start, end: range.end } : {}
784
+ start: range?.start ?? 0,
785
+ ...range ? { end: range.end } : {}
772
786
  });
773
787
  streamOwnsFd = true;
774
788
  try {
@@ -782,10 +796,10 @@ function createFileServer(options) {
782
796
  if (!streamOwnsFd) closeSync(fd);
783
797
  }
784
798
  });
785
- return new Promise((resolve10) => {
799
+ return new Promise((resolve11) => {
786
800
  const connections = /* @__PURE__ */ new Set();
787
801
  const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
788
- resolve10({
802
+ resolve11({
789
803
  url: `http://localhost:${info.port}`,
790
804
  port: info.port,
791
805
  addPreHeadScript: (script) => {
@@ -1292,8 +1306,8 @@ var OrderedRenderEventPublisher = class {
1292
1306
  let timer;
1293
1307
  const drained = await Promise.race([
1294
1308
  this.tail.then(() => true),
1295
- new Promise((resolve10) => {
1296
- timer = setTimeout(() => resolve10(false), FLUSH_TIMEOUT_MS);
1309
+ new Promise((resolve11) => {
1310
+ timer = setTimeout(() => resolve11(false), FLUSH_TIMEOUT_MS);
1297
1311
  })
1298
1312
  ]);
1299
1313
  clearTimeout(timer);
@@ -3636,7 +3650,7 @@ var SequentialCaptureStallError = class extends Error {
3636
3650
  }
3637
3651
  };
3638
3652
  function raceAgainstStall(promise, deadlineMs, input, signal) {
3639
- return new Promise((resolve10, reject) => {
3653
+ return new Promise((resolve11, reject) => {
3640
3654
  const timer = setTimeout(
3641
3655
  () => {
3642
3656
  reject(
@@ -3655,7 +3669,7 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3655
3669
  promise.then(
3656
3670
  (value) => {
3657
3671
  clearTimeout(timer);
3658
- resolve10(value);
3672
+ resolve11(value);
3659
3673
  },
3660
3674
  (error) => {
3661
3675
  clearTimeout(timer);
@@ -5583,15 +5597,15 @@ function countAuthoredTimedClips(html) {
5583
5597
  }
5584
5598
 
5585
5599
  // src/services/render/stages/compileStage.ts
5586
- import { join as join15 } from "node:path";
5600
+ import { join as join16 } from "node:path";
5587
5601
  import { suggestMatchingPreset } from "@hyperframes/core";
5588
5602
 
5589
5603
  // src/services/htmlCompiler.ts
5590
5604
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5591
- import { createHash as createHash5 } from "node:crypto";
5592
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
5593
- import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
5594
- import { parseHTML as parseHTML4 } from "linkedom";
5605
+ import { createHash as createHash6 } from "node:crypto";
5606
+ import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5607
+ import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
5608
+ import { parseHTML as parseHTML5 } from "linkedom";
5595
5609
  import {
5596
5610
  COMPOSITION_SOURCE_URL,
5597
5611
  compileTimingAttrs,
@@ -5867,7 +5881,7 @@ function readLoopOverride(el) {
5867
5881
  }
5868
5882
  return null;
5869
5883
  }
5870
- function resolveGifSourcePath(src, options) {
5884
+ function resolveLocalImageSourcePath(src, options) {
5871
5885
  const trimmed = src.trim();
5872
5886
  if (!trimmed || trimmed.startsWith("data:")) return null;
5873
5887
  const { basePath } = splitUrlSuffix(trimmed);
@@ -6090,7 +6104,7 @@ async function prepareAnimatedGifInputs(html, options) {
6090
6104
  const img = images[i];
6091
6105
  const src = (img.getAttribute("src") || "").trim();
6092
6106
  if (!hasGifExtension(src)) continue;
6093
- const sourcePath = resolveGifSourcePath(src, options);
6107
+ const sourcePath = resolveLocalImageSourcePath(src, options);
6094
6108
  if (!sourcePath) continue;
6095
6109
  const bytes = readFileSync5(sourcePath);
6096
6110
  const metadata = parseAnimatedGifMetadata(bytes);
@@ -6138,19 +6152,74 @@ async function prepareAnimatedGifInputs(html, options) {
6138
6152
  };
6139
6153
  }
6140
6154
 
6141
- // src/services/htmlCompiler.ts
6142
- import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6143
- import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6144
-
6145
- // src/services/assetMediaType.ts
6146
- import { existsSync as existsSync9 } from "node:fs";
6155
+ // src/services/heifImagePrep.ts
6156
+ import { createHash as createHash5 } from "node:crypto";
6147
6157
  import {
6148
- fingerprintElementId,
6149
- isNotMediaPayload,
6150
- NotMediaPayloadError,
6151
- probeMediaProfile,
6152
- resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6153
- } from "@hyperframes/engine";
6158
+ closeSync as closeSync4,
6159
+ copyFileSync as copyFileSync3,
6160
+ mkdirSync as mkdirSync6,
6161
+ openSync as openSync4,
6162
+ readFileSync as readFileSync6,
6163
+ readSync as readSync3,
6164
+ rmSync as rmSync8,
6165
+ statSync as statSync6,
6166
+ writeFileSync as writeFileSync4
6167
+ } from "node:fs";
6168
+ import { dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
6169
+ import { parseHTML as parseHTML4 } from "linkedom";
6170
+ import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
6171
+
6172
+ // src/services/heifRotation.ts
6173
+ function* boxes(buf, start, end) {
6174
+ let at = start;
6175
+ while (at + 8 <= end) {
6176
+ let size = buf.readUInt32BE(at);
6177
+ let header = 8;
6178
+ if (size === 1 && at + 16 <= end) {
6179
+ size = Number(buf.readBigUInt64BE(at + 8));
6180
+ header = 16;
6181
+ } else if (size === 0) {
6182
+ size = end - at;
6183
+ }
6184
+ if (size < header || at + size > end) return;
6185
+ yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
6186
+ at += size;
6187
+ }
6188
+ }
6189
+ function child(buf, parent, type, skip = 0) {
6190
+ return [...boxes(buf, parent.start + skip, parent.end)].filter((box) => box.type === type);
6191
+ }
6192
+ function readHeifPrimaryRotation(buf) {
6193
+ try {
6194
+ const root = { type: "", start: 0, end: buf.length };
6195
+ const meta = child(buf, root, "meta")[0];
6196
+ const pitm = meta && child(buf, meta, "pitm", 4)[0];
6197
+ const iprp = meta && child(buf, meta, "iprp", 4)[0];
6198
+ const ipco = iprp && child(buf, iprp, "ipco")[0];
6199
+ if (!pitm || !ipco || !iprp) return 0;
6200
+ const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
6201
+ const props = [...boxes(buf, ipco.start, ipco.end)];
6202
+ for (const ipma of child(buf, iprp, "ipma")) {
6203
+ const version = buf[ipma.start];
6204
+ const wide = (buf[ipma.start + 3] & 1) === 1;
6205
+ let at = ipma.start + 8;
6206
+ for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
6207
+ const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
6208
+ at += version < 1 ? 2 : 4;
6209
+ const count = buf[at++];
6210
+ for (let i = 0; i < count; i++) {
6211
+ const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
6212
+ at += wide ? 2 : 1;
6213
+ const prop = props[index - 1];
6214
+ if (id === primaryId && prop?.type === "irot")
6215
+ return (buf[prop.start] & 3) * 90;
6216
+ }
6217
+ }
6218
+ }
6219
+ } catch {
6220
+ }
6221
+ return 0;
6222
+ }
6154
6223
 
6155
6224
  // src/utils/semaphore.ts
6156
6225
  var Semaphore = class {
@@ -6165,10 +6234,10 @@ var Semaphore = class {
6165
6234
  this.active++;
6166
6235
  return () => this.release();
6167
6236
  }
6168
- return new Promise((resolve10) => {
6237
+ return new Promise((resolve11) => {
6169
6238
  this.queue.push(() => {
6170
6239
  this.active++;
6171
- resolve10(() => this.release());
6240
+ resolve11(() => this.release());
6172
6241
  });
6173
6242
  });
6174
6243
  }
@@ -6199,7 +6268,229 @@ async function withMediaProbeSlot(operation) {
6199
6268
  }
6200
6269
  }
6201
6270
 
6271
+ // src/services/heifImagePrep.ts
6272
+ var PREPARED_HEIF_SUBDIR = "_heif_images";
6273
+ var CACHE_SCHEMA2 = "hfheif-v1";
6274
+ var DEFAULT_TIMEOUT_MS = 12e4;
6275
+ var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
6276
+ var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
6277
+ var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
6278
+ var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
6279
+ var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
6280
+ var HeifImageConversionError = class extends Error {
6281
+ code = HEIF_IMAGE_CONVERSION_FAILED;
6282
+ owner = "user";
6283
+ retryable = false;
6284
+ constructor(label, reason) {
6285
+ super(
6286
+ `Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
6287
+ );
6288
+ this.name = "HeifImageConversionError";
6289
+ }
6290
+ };
6291
+ var HeifToolUnavailableError = class extends Error {
6292
+ code = HEIF_TOOL_UNAVAILABLE;
6293
+ owner = "system";
6294
+ retryable = false;
6295
+ constructor(label, reason) {
6296
+ super(
6297
+ `Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
6298
+ );
6299
+ this.name = "HeifToolUnavailableError";
6300
+ }
6301
+ };
6302
+ var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
6303
+ function isHeifBytes(head) {
6304
+ const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
6305
+ if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
6306
+ const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
6307
+ if (boxEnd < 16) return false;
6308
+ const brands = [bytes.toString("ascii", 8, 12)];
6309
+ for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
6310
+ brands.push(bytes.toString("ascii", offset, offset + 4));
6311
+ }
6312
+ return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
6313
+ }
6314
+ function readHead(path) {
6315
+ let fd;
6316
+ try {
6317
+ fd = openSync4(path, "r");
6318
+ const buf = Buffer.alloc(4096);
6319
+ return buf.subarray(0, readSync3(fd, buf, 0, buf.length, 0));
6320
+ } catch {
6321
+ return null;
6322
+ } finally {
6323
+ if (fd !== void 0) closeSync4(fd);
6324
+ }
6325
+ }
6326
+ async function probeJson(path, withGroups) {
6327
+ const stdout = await runFfprobe(
6328
+ path,
6329
+ withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
6330
+ );
6331
+ return JSON.parse(stdout);
6332
+ }
6333
+ var ROTATION_FILTER = {
6334
+ 0: "",
6335
+ 90: "transpose=2",
6336
+ 180: "hflip,vflip",
6337
+ 270: "transpose=1"
6338
+ };
6339
+ function primaryGrid(probed) {
6340
+ const groups = probed.stream_groups ?? [];
6341
+ return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
6342
+ }
6343
+ async function convertHeifToJpeg(request) {
6344
+ const probed = await probeJson(request.inputPath, true);
6345
+ const grid = primaryGrid(probed);
6346
+ const rotation = readHeifPrimaryRotation(readFileSync6(request.inputPath));
6347
+ const filters = [];
6348
+ let inputs = [];
6349
+ let size = probed.streams?.[0];
6350
+ if (grid?.subcomponents?.length) {
6351
+ size = grid;
6352
+ const layout = grid.subcomponents.map(
6353
+ (t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
6354
+ );
6355
+ inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
6356
+ filters.push(
6357
+ `${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
6358
+ `crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
6359
+ );
6360
+ }
6361
+ if (!size?.width || !size.height) throw new Error("no declared image size");
6362
+ if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
6363
+ const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
6364
+ const result = await runFfmpeg3(
6365
+ [
6366
+ "-hide_banner",
6367
+ "-loglevel",
6368
+ "error",
6369
+ "-i",
6370
+ request.inputPath,
6371
+ ...mapArgs,
6372
+ "-frames:v",
6373
+ "1",
6374
+ "-q:v",
6375
+ "2",
6376
+ "-f",
6377
+ "mjpeg",
6378
+ "-y",
6379
+ request.outputPath
6380
+ ],
6381
+ { timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
6382
+ );
6383
+ if (!result.success) {
6384
+ throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
6385
+ }
6386
+ const out = (await probeJson(request.outputPath, false)).streams?.[0];
6387
+ const same = out?.width === size.width && out.height === size.height;
6388
+ const swapped = out?.width === size.height && out.height === size.width;
6389
+ if (!same && !swapped) {
6390
+ throw new Error(
6391
+ `wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
6392
+ );
6393
+ }
6394
+ }
6395
+ function resolveImageSource(src, options) {
6396
+ const data = DATA_URL_RE.exec(src);
6397
+ if (data) {
6398
+ const bytes = Buffer.from(data[1], "base64");
6399
+ return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
6400
+ }
6401
+ const path = resolveLocalImageSourcePath(src, options);
6402
+ const head = path ? readHead(path) : null;
6403
+ if (!path || !head || !isHeifBytes(head)) return null;
6404
+ return { path, bytes: readFileSync6(path), label: `"${splitUrlSuffix(src).basePath}"` };
6405
+ }
6406
+ function isUsableFile2(path) {
6407
+ try {
6408
+ return statSync6(path).size > 0;
6409
+ } catch {
6410
+ return false;
6411
+ }
6412
+ }
6413
+ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6414
+ mkdirSync6(dirname6(cachePath), { recursive: true });
6415
+ mkdirSync6(dirname6(outputPath), { recursive: true });
6416
+ if (!isUsableFile2(cachePath)) {
6417
+ const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
6418
+ const inlinePath = `${tmpPath}.heic`;
6419
+ const inputPath = source.path ?? inlinePath;
6420
+ try {
6421
+ if (!source.path) writeFileSync4(inlinePath, source.bytes);
6422
+ await (options.transcode ?? convertHeifToJpeg)({
6423
+ inputPath,
6424
+ outputPath: tmpPath,
6425
+ timeoutMs: options.timeoutMs
6426
+ });
6427
+ if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
6428
+ renameWithRetry4(tmpPath, cachePath);
6429
+ } catch (error) {
6430
+ const reason = error instanceof Error ? error.message : String(error);
6431
+ throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
6432
+ } finally {
6433
+ rmSync8(tmpPath, { force: true });
6434
+ rmSync8(inlinePath, { force: true });
6435
+ }
6436
+ }
6437
+ if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
6438
+ copyFileSync3(cachePath, outputPath);
6439
+ }
6440
+ }
6441
+ async function prepareHeifImageInputs(html, options) {
6442
+ const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
6443
+ const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
6444
+ const cacheDir = options.cacheDir ?? outputDir;
6445
+ const preparedAssets = /* @__PURE__ */ new Map();
6446
+ const preparedImages = [];
6447
+ if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
6448
+ const { document: document2 } = parseHTML4(html);
6449
+ const found = [];
6450
+ for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
6451
+ const src = (img.getAttribute("src") || "").trim();
6452
+ const source = src ? resolveImageSource(src, options) : null;
6453
+ if (!source) continue;
6454
+ const hash = createHash5("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
6455
+ found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
6456
+ }
6457
+ if (found.length === 0) return { html, preparedAssets, preparedImages };
6458
+ const unique = new Map(found.map((item) => [item.filename, item.source]));
6459
+ const failed = /* @__PURE__ */ new Set();
6460
+ await Promise.all(
6461
+ [...unique].map(
6462
+ ([filename, source]) => withMediaProbeSlot(
6463
+ () => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
6464
+ ).catch((error) => {
6465
+ if (!options.onFailure) throw error;
6466
+ failed.add(filename);
6467
+ options.onFailure(error);
6468
+ })
6469
+ )
6470
+ );
6471
+ for (const { img, src, filename } of found) {
6472
+ if (failed.has(filename)) continue;
6473
+ const outputSrc = `${outputSrcPrefix}/${filename}`;
6474
+ img.setAttribute("src", outputSrc);
6475
+ preparedAssets.set(outputSrc, join14(outputDir, filename));
6476
+ preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join14(outputDir, filename) });
6477
+ }
6478
+ return { html: document2.toString(), preparedAssets, preparedImages };
6479
+ }
6480
+
6481
+ // src/services/htmlCompiler.ts
6482
+ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6483
+ import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6484
+
6202
6485
  // src/services/assetMediaType.ts
6486
+ import { existsSync as existsSync9 } from "node:fs";
6487
+ import {
6488
+ fingerprintElementId,
6489
+ isNotMediaPayload,
6490
+ NotMediaPayloadError,
6491
+ probeMediaProfile,
6492
+ resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6493
+ } from "@hyperframes/engine";
6203
6494
  var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
6204
6495
  var AssetMediaTypeMismatchError = class extends Error {
6205
6496
  code = ASSET_MEDIA_TYPE_MISMATCH;
@@ -6309,7 +6600,7 @@ function logRemoteDownloadTelemetry(event) {
6309
6600
  }
6310
6601
  var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
6311
6602
  function parseSubCompHtmlForValidity(html) {
6312
- return parseHTML4(html).document;
6603
+ return parseHTML5(html).document;
6313
6604
  }
6314
6605
  function endsWithSourceUrl(code) {
6315
6606
  const trimmed = code.trimEnd();
@@ -6317,7 +6608,7 @@ function endsWithSourceUrl(code) {
6317
6608
  return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
6318
6609
  }
6319
6610
  function prepareCompositionScripts(html) {
6320
- const { document: document2 } = parseHTML4(html);
6611
+ const { document: document2 } = parseHTML5(html);
6321
6612
  for (const el of document2.querySelectorAll("script:not([src])")) {
6322
6613
  const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
6323
6614
  const runsAsScript = isModule || isJavaScriptType(el);
@@ -6354,14 +6645,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
6354
6645
  }
6355
6646
  };
6356
6647
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
6357
- const { document: document2 } = parseHTML4(html);
6648
+ const { document: document2 } = parseHTML5(html);
6358
6649
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
6359
6650
  const problems = [];
6360
6651
  for (const el of hosts) {
6361
6652
  const srcPath = el.getAttribute("data-composition-src");
6362
6653
  if (!srcPath) continue;
6363
6654
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
6364
- const filePath = resolve6(projectDir, srcPath);
6655
+ const filePath = resolve7(projectDir, srcPath);
6365
6656
  if (visited.has(filePath)) continue;
6366
6657
  const read = readProjectFile(filePath);
6367
6658
  if (read.kind === "missing") {
@@ -6417,7 +6708,7 @@ function runtimeRoot(document2) {
6417
6708
  }
6418
6709
  function detectRenderModeHints(html, pageMotionBlur) {
6419
6710
  const reasons = [];
6420
- const { document: document2 } = parseHTML4(html);
6711
+ const { document: document2 } = parseHTML5(html);
6421
6712
  if (document2.querySelector("canvas[layoutsubtree]")) {
6422
6713
  reasons.push({
6423
6714
  code: "htmlInCanvas",
@@ -6459,7 +6750,7 @@ function detectThreeDTransformUsage(html) {
6459
6750
  }
6460
6751
  var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
6461
6752
  function detectAncestorBackgroundImage(html) {
6462
- const { document: document2 } = parseHTML4(html);
6753
+ const { document: document2 } = parseHTML5(html);
6463
6754
  const root = document2.querySelector("[data-composition-id]");
6464
6755
  if (!root) return false;
6465
6756
  const ancestors = [];
@@ -6506,7 +6797,7 @@ function detectShaderTransitionUsage(html) {
6506
6797
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6507
6798
  let filePath = src;
6508
6799
  if (isHttpUrl(src)) {
6509
- if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
6800
+ if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
6510
6801
  try {
6511
6802
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
6512
6803
  onTelemetry: logRemoteDownloadTelemetry
@@ -6516,7 +6807,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6516
6807
  }
6517
6808
  } else {
6518
6809
  filePath = decodedUrlPath(src);
6519
- if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
6810
+ if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
6520
6811
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6521
6812
  }
6522
6813
  if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
@@ -6573,7 +6864,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6573
6864
  function markInferredVariableMediaDurations(html, unresolvedMedia) {
6574
6865
  const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
6575
6866
  if (unresolvedIds.size === 0) return html;
6576
- const { document: document2 } = parseHTML4(html);
6867
+ const { document: document2 } = parseHTML5(html);
6577
6868
  let changed = false;
6578
6869
  for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
6579
6870
  if (!unresolvedIds.has(element.id)) continue;
@@ -6644,13 +6935,13 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
6644
6935
  }
6645
6936
  async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6646
6937
  const subCompositions = /* @__PURE__ */ new Map();
6647
- const { document: document2 } = parseHTML4(html);
6938
+ const { document: document2 } = parseHTML5(html);
6648
6939
  const compEls = document2.querySelectorAll("[data-composition-src]");
6649
6940
  const workItems = [];
6650
6941
  for (const el of compEls) {
6651
6942
  const srcPath = el.getAttribute("data-composition-src");
6652
6943
  if (!srcPath) continue;
6653
- const filePath = resolve6(projectDir, srcPath);
6944
+ const filePath = resolve7(projectDir, srcPath);
6654
6945
  if (visited.has(filePath)) {
6655
6946
  continue;
6656
6947
  }
@@ -6667,7 +6958,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6667
6958
  workItems.map(async (item) => {
6668
6959
  const { html: compiledSub } = await compileHtmlFile(
6669
6960
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
6670
- dirname6(item.filePath),
6961
+ dirname7(item.filePath),
6671
6962
  downloadDir,
6672
6963
  void 0,
6673
6964
  urlByAbsentLocalPath
@@ -6695,7 +6986,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6695
6986
  return { subCompositions };
6696
6987
  }
6697
6988
  function promoteCssImportsToLinkTags(html) {
6698
- const { document: document2 } = parseHTML4(html);
6989
+ const { document: document2 } = parseHTML5(html);
6699
6990
  const head = document2.querySelector("head");
6700
6991
  if (!head) return html;
6701
6992
  const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
@@ -6764,7 +7055,7 @@ var ProducerHostIdentityMap = class extends Map {
6764
7055
  }
6765
7056
  };
6766
7057
  function coalesceHeadStylesAndBodyScripts(html) {
6767
- const { document: document2 } = parseHTML4(html);
7058
+ const { document: document2 } = parseHTML5(html);
6768
7059
  const head = document2.querySelector("head");
6769
7060
  const body = document2.querySelector("body");
6770
7061
  if (!head && !body) return html;
@@ -6815,7 +7106,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
6815
7106
  return document2.toString();
6816
7107
  }
6817
7108
  function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
6818
- const { document: document2 } = parseHTML4(html);
7109
+ const { document: document2 } = parseHTML5(html);
6819
7110
  const head = document2.querySelector("head");
6820
7111
  const body = document2.querySelector("body");
6821
7112
  const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
@@ -6844,17 +7135,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
6844
7135
  resolveHtml: (srcPath) => {
6845
7136
  let compHtml = subCompositions.get(srcPath) || null;
6846
7137
  if (!compHtml) {
6847
- const read = readProjectFile(resolve6(projectDir, srcPath));
7138
+ const read = readProjectFile(resolve7(projectDir, srcPath));
6848
7139
  if (read.kind === "file") {
6849
7140
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
6850
7141
  }
6851
7142
  }
6852
7143
  return compHtml;
6853
7144
  },
6854
- parseHtml: (htmlStr) => parseHTML4(htmlStr).document,
7145
+ parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
6855
7146
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
6856
7147
  // against its own directory, project-root refs stay as authored.
6857
- assetExists: (path) => existsSync10(resolve6(projectDir, path)),
7148
+ assetExists: (path) => existsSync10(resolve7(projectDir, path)),
6858
7149
  // Preserve the authored root wrapper as a child of the host, matching
6859
7150
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
6860
7151
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -6955,7 +7246,7 @@ ${html}
6955
7246
  </html>`;
6956
7247
  }
6957
7248
  function injectTextRenderingRule(html) {
6958
- const { document: document2 } = parseHTML4(html);
7249
+ const { document: document2 } = parseHTML5(html);
6959
7250
  const head = document2.querySelector("head");
6960
7251
  if (!head) return html;
6961
7252
  if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
@@ -6979,7 +7270,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6979
7270
  (alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
6980
7271
  );
6981
7272
  if (!strongest) return true;
6982
- const actual = createHash5(strongest).update(bytes).digest();
7273
+ const actual = createHash6(strongest).update(bytes).digest();
6983
7274
  return hashes.some(
6984
7275
  (hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
6985
7276
  );
@@ -6987,7 +7278,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6987
7278
  async function inlineExternalScripts(html) {
6988
7279
  const fullHtml = ensureFullDocument(html);
6989
7280
  const wrappedFragment = fullHtml !== html;
6990
- const { document: document2 } = parseHTML4(fullHtml);
7281
+ const { document: document2 } = parseHTML5(fullHtml);
6991
7282
  const scripts = document2.querySelectorAll("script[src]");
6992
7283
  const externalScripts = [];
6993
7284
  for (const el of scripts) {
@@ -7036,12 +7327,12 @@ ${safeText}
7036
7327
  return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
7037
7328
  }
7038
7329
  function collectExternalAssets(html, projectDir) {
7039
- const absProjectDir = resolve6(projectDir);
7330
+ const absProjectDir = resolve7(projectDir);
7040
7331
  const externalAssets = /* @__PURE__ */ new Map();
7041
7332
  function processPath(rawPath) {
7042
7333
  const trimmed = rawPath.trim();
7043
7334
  if (isNonRelativeUrl(trimmed)) return null;
7044
- const absPath = resolve6(absProjectDir, trimmed);
7335
+ const absPath = resolve7(absProjectDir, trimmed);
7045
7336
  if (isPathInside2(absPath, absProjectDir)) {
7046
7337
  return null;
7047
7338
  }
@@ -7050,7 +7341,7 @@ function collectExternalAssets(html, projectDir) {
7050
7341
  externalAssets.set(safeKey, absPath);
7051
7342
  return safeKey;
7052
7343
  }
7053
- const { document: document2 } = parseHTML4(html);
7344
+ const { document: document2 } = parseHTML5(html);
7054
7345
  for (const el of document2.querySelectorAll("[src], [href]")) {
7055
7346
  for (const attr of ["src", "href"]) {
7056
7347
  const val = (el.getAttribute(attr) || "").trim();
@@ -7095,7 +7386,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
7095
7386
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
7096
7387
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
7097
7388
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
7098
- if (!existsSync10(remoteDir)) mkdirSync6(remoteDir, { recursive: true });
7389
+ if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
7099
7390
  const urlToLocal = /* @__PURE__ */ new Map();
7100
7391
  await Promise.all(
7101
7392
  [...urlSet].map(async (url) => {
@@ -7142,7 +7433,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
7142
7433
  return downloadAndRewriteUrls(
7143
7434
  urlSet,
7144
7435
  html,
7145
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7436
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7146
7437
  "Remote media download failed",
7147
7438
  "Localized remote media source(s)"
7148
7439
  );
@@ -7157,7 +7448,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
7157
7448
  return downloadAndRewriteUrls(
7158
7449
  urlSet,
7159
7450
  html,
7160
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7451
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7161
7452
  "Remote image download failed",
7162
7453
  "Localized remote image source(s)"
7163
7454
  );
@@ -7173,7 +7464,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
7173
7464
  return downloadAndRewriteUrls(
7174
7465
  urlSet,
7175
7466
  html,
7176
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7467
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7177
7468
  "Remote background-image download failed",
7178
7469
  "Localized remote background-image(s)",
7179
7470
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -7308,7 +7599,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
7308
7599
  return downloadAndRewriteUrls(
7309
7600
  urlSet,
7310
7601
  processed,
7311
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7602
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7312
7603
  "Remote font download failed",
7313
7604
  "Localized remote font face(s)",
7314
7605
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -7353,7 +7644,7 @@ async function embedLocalFontFaces(html, projectDir) {
7353
7644
  while ((urlMatch = urlRe.exec(block)) !== null) {
7354
7645
  const localPath = urlMatch[1];
7355
7646
  if (!localPath || embeddedPaths.has(localPath)) continue;
7356
- const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
7647
+ const absPath = localPath.startsWith("/") ? localPath : resolve7(projectDir, localPath);
7357
7648
  if (!isPathInside2(absPath, projectDir)) continue;
7358
7649
  const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
7359
7650
  try {
@@ -7402,7 +7693,7 @@ function renderGsapDist() {
7402
7693
  }
7403
7694
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
7404
7695
  let gsapDist;
7405
- const exists = (path) => existsSync10(resolve6(projectDir, path));
7696
+ const exists = (path) => existsSync10(resolve7(projectDir, path));
7406
7697
  return html.replace(
7407
7698
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
7408
7699
  (full, prefix, src, file, suffix) => {
@@ -7418,8 +7709,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7418
7709
  if (!isPathInside2(htmlPath, projectDir)) return html;
7419
7710
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
7420
7711
  if (!entryPath.includes("/")) return html;
7421
- const { document: document2 } = parseHTML4(html);
7422
- const assetExists = (path) => existsSync10(resolve6(projectDir, path));
7712
+ const { document: document2 } = parseHTML5(html);
7713
+ const assetExists = (path) => existsSync10(resolve7(projectDir, path));
7423
7714
  rewriteAssetPaths(
7424
7715
  document2.querySelectorAll("[src], [href]"),
7425
7716
  entryPath,
@@ -7441,7 +7732,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7441
7732
  }
7442
7733
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
7443
7734
  const entryHtml = rebaseDirectEntryAssetPaths(
7444
- readFileSync6(htmlPath, "utf-8"),
7735
+ readFileSync7(htmlPath, "utf-8"),
7445
7736
  projectDir,
7446
7737
  htmlPath
7447
7738
  );
@@ -7511,12 +7802,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7511
7802
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
7512
7803
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
7513
7804
  const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
7805
+ const {
7806
+ html: htmlWithJpegs,
7807
+ preparedAssets: preparedHeifAssets,
7808
+ preparedImages: preparedHeifImages
7809
+ } = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
7810
+ projectDir,
7811
+ downloadDir,
7812
+ cacheDir: options.animatedGifCacheDir,
7813
+ sourceAssets: remoteImageAssets,
7814
+ timeoutMs: options.ffmpegProcessTimeout
7815
+ });
7816
+ if (preparedHeifImages.length > 0) {
7817
+ defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
7818
+ }
7514
7819
  const gifSourceAssets = new Map(remoteImageAssets);
7515
7820
  const {
7516
7821
  html: htmlWithPreparedGifs,
7517
7822
  preparedAssets: preparedGifAssets,
7518
7823
  preparedGifs
7519
- } = await prepareAnimatedGifInputs(htmlWithLocalizedFonts, {
7824
+ } = await prepareAnimatedGifInputs(htmlWithJpegs, {
7520
7825
  projectDir,
7521
7826
  downloadDir,
7522
7827
  cacheDir: options.animatedGifCacheDir,
@@ -7543,11 +7848,14 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7543
7848
  for (const [relPath, absPath] of preparedGifAssets) {
7544
7849
  externalAssets.set(relPath, absPath);
7545
7850
  }
7851
+ for (const [relPath, absPath] of preparedHeifAssets) {
7852
+ externalAssets.set(relPath, absPath);
7853
+ }
7546
7854
  const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
7547
7855
  const masterFxChain = parseMasterFxChain(html);
7548
7856
  for (const video of videos) {
7549
7857
  if (isHttpUrl(video.src)) continue;
7550
- const videoPath = resolve6(projectDir, video.src);
7858
+ const videoPath = resolve7(projectDir, video.src);
7551
7859
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
7552
7860
  (async () => {
7553
7861
  const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
@@ -7567,7 +7875,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7567
7875
  })().catch(() => {
7568
7876
  });
7569
7877
  }
7570
- const { document: document2 } = parseHTML4(html);
7878
+ const { document: document2 } = parseHTML5(html);
7571
7879
  const rootEl = document2.querySelector("[data-composition-id]");
7572
7880
  const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
7573
7881
  const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
@@ -7974,7 +8282,7 @@ async function runCompileStage(input) {
7974
8282
  abortSignal
7975
8283
  } = input;
7976
8284
  const compileStart = Date.now();
7977
- const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
8285
+ const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
7978
8286
  log,
7979
8287
  failClosedFontFetch: failClosedFontFetch === true,
7980
8288
  allowSystemFontCapture,
@@ -7982,7 +8290,7 @@ async function runCompileStage(input) {
7982
8290
  variables: input.variables,
7983
8291
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7984
8292
  pageMotionBlur: input.pageMotionBlur,
7985
- animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
8293
+ animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
7986
8294
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7987
8295
  });
7988
8296
  assertNotAborted();
@@ -8038,7 +8346,7 @@ async function runCompileStage(input) {
8038
8346
  };
8039
8347
  await preflightCompositionAssetMediaTypes({
8040
8348
  projectDir,
8041
- compiledDir: join15(workDir, "compiled"),
8349
+ compiledDir: join16(workDir, "compiled"),
8042
8350
  composition,
8043
8351
  signal: abortSignal
8044
8352
  });
@@ -8083,8 +8391,8 @@ async function runCompileStage(input) {
8083
8391
  }
8084
8392
 
8085
8393
  // src/services/render/stages/probeStage.ts
8086
- import { join as join16 } from "node:path";
8087
- import { parseHTML as parseHTML5 } from "linkedom";
8394
+ import { join as join17 } from "node:path";
8395
+ import { parseHTML as parseHTML6 } from "linkedom";
8088
8396
  import {
8089
8397
  closeCaptureSession as closeCaptureSession4,
8090
8398
  compositionRequiresWebGpu,
@@ -8131,7 +8439,7 @@ function durationToFrameCount(duration, fps) {
8131
8439
  }
8132
8440
  function hasScriptedAudioVolumeAutomation(html, audioCount) {
8133
8441
  if (audioCount <= 0) return false;
8134
- const { document: document2 } = parseHTML5(html);
8442
+ const { document: document2 } = parseHTML6(html);
8135
8443
  const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
8136
8444
  if (!scriptBodies) return false;
8137
8445
  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(
@@ -8139,12 +8447,12 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
8139
8447
  );
8140
8448
  }
8141
8449
  function hasAutoStartVideos(html) {
8142
- const { document: document2 } = parseHTML5(html);
8450
+ const { document: document2 } = parseHTML6(html);
8143
8451
  return document2.querySelector("video[data-hf-auto-start]") !== null;
8144
8452
  }
8145
8453
  function hasVariableBoundMedia(html, variables) {
8146
8454
  if (!variables || Object.keys(variables).length === 0) return false;
8147
- const { document: document2 } = parseHTML5(html);
8455
+ const { document: document2 } = parseHTML6(html);
8148
8456
  return Array.from(
8149
8457
  document2.querySelectorAll(
8150
8458
  "img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
@@ -8155,7 +8463,7 @@ function hasVariableBoundMedia(html, variables) {
8155
8463
  });
8156
8464
  }
8157
8465
  function authoredMediaStarts(html) {
8158
- const { document: document2 } = parseHTML5(html);
8466
+ const { document: document2 } = parseHTML6(html);
8159
8467
  const starts = /* @__PURE__ */ new Map();
8160
8468
  for (const media of document2.querySelectorAll("video, audio")) {
8161
8469
  const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
@@ -8192,7 +8500,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
8192
8500
  return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
8193
8501
  }
8194
8502
  function hasRuntimeMediaChanges(html) {
8195
- const { document: document2 } = parseHTML5(html);
8503
+ const { document: document2 } = parseHTML6(html);
8196
8504
  const scriptBodies = [...document2.querySelectorAll("script")].map(
8197
8505
  (script) => script.textContent ?? ""
8198
8506
  );
@@ -8275,7 +8583,7 @@ async function runProbeStage(input) {
8275
8583
  });
8276
8584
  fileServer = await createFileServer({
8277
8585
  projectDir,
8278
- compiledDir: join16(workDir, "compiled"),
8586
+ compiledDir: join17(workDir, "compiled"),
8279
8587
  port: 0,
8280
8588
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8281
8589
  fps: job.config.fps
@@ -8304,7 +8612,7 @@ async function runProbeStage(input) {
8304
8612
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
8305
8613
  probeSession = await createCaptureSession4(
8306
8614
  fileServer.url,
8307
- join16(workDir, "probe"),
8615
+ join17(workDir, "probe"),
8308
8616
  captureOpts,
8309
8617
  null,
8310
8618
  probeCfg
@@ -8389,7 +8697,7 @@ async function runProbeStage(input) {
8389
8697
  });
8390
8698
  probeSession = await createCaptureSession4(
8391
8699
  fileServer.url,
8392
- join16(workDir, "probe-screenshot"),
8700
+ join17(workDir, "probe-screenshot"),
8393
8701
  captureOpts,
8394
8702
  null,
8395
8703
  { ...probeCfg, forceScreenshot: true }
@@ -8425,7 +8733,7 @@ async function runProbeStage(input) {
8425
8733
  compiled,
8426
8734
  resolutions,
8427
8735
  projectDir,
8428
- join16(workDir, "downloads")
8736
+ join17(workDir, "downloads")
8429
8737
  );
8430
8738
  assertNotAborted();
8431
8739
  composition.videos = compiled.videos;
@@ -8616,7 +8924,7 @@ async function runProbeStage(input) {
8616
8924
  }
8617
8925
  await preflightCompositionAssetMediaTypes({
8618
8926
  projectDir,
8619
- compiledDir: join16(workDir, "compiled"),
8927
+ compiledDir: join17(workDir, "compiled"),
8620
8928
  composition,
8621
8929
  signal: abortSignal
8622
8930
  });
@@ -8750,7 +9058,7 @@ function validateRenderDuration(input) {
8750
9058
 
8751
9059
  // src/services/render/stages/extractVideosStage.ts
8752
9060
  import { existsSync as existsSync11 } from "node:fs";
8753
- import { join as join17 } from "node:path";
9061
+ import { join as join18 } from "node:path";
8754
9062
  import {
8755
9063
  classifyVideoExtractionError,
8756
9064
  createFrameLookupTable,
@@ -9065,14 +9373,14 @@ async function runExtractVideosStage(input) {
9065
9373
  // because short boundary counts can differ by one frame.
9066
9374
  {
9067
9375
  fps: job.config.fps,
9068
- outputDir: join17(compiledDir, "__hyperframes_video_frames"),
9376
+ outputDir: join18(compiledDir, "__hyperframes_video_frames"),
9069
9377
  format: job.config.videoFrameFormat ?? "auto",
9070
9378
  toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
9071
9379
  timelineEnd: composition.duration,
9072
9380
  maxTransientRetries: extractionPolicy.maxTransientRetries,
9073
9381
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
9074
9382
  deferRangeExtraction,
9075
- contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
9383
+ contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
9076
9384
  urlByAbsentLocalPath
9077
9385
  },
9078
9386
  abortSignal,
@@ -9139,8 +9447,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
9139
9447
  }
9140
9448
 
9141
9449
  // src/services/render/stages/audioStage.ts
9142
- import { join as join19 } from "node:path";
9143
- import { rmSync as rmSync9 } from "node:fs";
9450
+ import { join as join20 } from "node:path";
9451
+ import { rmSync as rmSync10 } from "node:fs";
9144
9452
  import {
9145
9453
  MIXED_AUDIO_FILENAME,
9146
9454
  audioFailureDiagnostics,
@@ -9149,8 +9457,8 @@ import {
9149
9457
 
9150
9458
  // src/services/render/audioPadTrim.ts
9151
9459
  import { spawn } from "node:child_process";
9152
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
9153
- import { dirname as dirname7, join as join18 } from "node:path";
9460
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync9 } from "node:fs";
9461
+ import { dirname as dirname8, join as join19 } from "node:path";
9154
9462
  import {
9155
9463
  DEFAULT_CONFIG,
9156
9464
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -9158,8 +9466,8 @@ import {
9158
9466
  extractAudioMetadata as extractAudioMetadata3,
9159
9467
  getFfprobeBinary,
9160
9468
  ManagedChildProcess,
9161
- renameWithRetry as renameWithRetry4,
9162
- runFfmpeg as runFfmpeg4,
9469
+ renameWithRetry as renameWithRetry5,
9470
+ runFfmpeg as runFfmpeg5,
9163
9471
  trackChildProcess
9164
9472
  } from "@hyperframes/engine";
9165
9473
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
@@ -9169,7 +9477,7 @@ import {
9169
9477
  aacSegmentCount,
9170
9478
  describeFfmpegFailure,
9171
9479
  extractAudioMetadata as extractAudioMetadata2,
9172
- runFfmpeg as runFfmpeg3
9480
+ runFfmpeg as runFfmpeg4
9173
9481
  } from "@hyperframes/engine";
9174
9482
  var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
9175
9483
  function checkedDbfs(value) {
@@ -9193,7 +9501,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
9193
9501
  else laterBlocks = Math.max(laterBlocks, block);
9194
9502
  }
9195
9503
  };
9196
- const result = await runFfmpeg3(
9504
+ const result = await runFfmpeg4(
9197
9505
  ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
9198
9506
  { signal, timeout: timeoutMs, onStderr: readLines }
9199
9507
  );
@@ -9353,7 +9661,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
9353
9661
  error: `audioPadTrim: failed to materialize ${plan.operation}: ${err instanceof Error ? err.message : String(err)}`
9354
9662
  };
9355
9663
  } finally {
9356
- for (const path of plan.cleanupPaths) rmSync8(path, { force: true });
9664
+ for (const path of plan.cleanupPaths) rmSync9(path, { force: true });
9357
9665
  }
9358
9666
  let loweredDb = 0;
9359
9667
  if (probeTruePeak) {
@@ -9400,8 +9708,8 @@ async function constrainAacTruePeak(input) {
9400
9708
  async function enforceAacTruePeak(input) {
9401
9709
  let scratchDir;
9402
9710
  try {
9403
- scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
9404
- const correctedPath = join18(scratchDir, "audio.m4a");
9711
+ scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
9712
+ const correctedPath = join19(scratchDir, "audio.m4a");
9405
9713
  const measurements = [];
9406
9714
  let attenuationDb = 0;
9407
9715
  let sourcePeakDbfs;
@@ -9416,7 +9724,7 @@ async function enforceAacTruePeak(input) {
9416
9724
  }
9417
9725
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
9418
9726
  if (measuredPath !== correctedPath) return { success: true };
9419
- renameWithRetry4(correctedPath, input.audioPath);
9727
+ renameWithRetry5(correctedPath, input.audioPath);
9420
9728
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
9421
9729
  }
9422
9730
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -9457,7 +9765,7 @@ async function enforceAacTruePeak(input) {
9457
9765
  )}`
9458
9766
  };
9459
9767
  } finally {
9460
- if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
9768
+ if (scratchDir) rmSync9(scratchDir, { recursive: true, force: true });
9461
9769
  }
9462
9770
  }
9463
9771
  function failResult(outputPath, target, source, error) {
@@ -9534,7 +9842,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
9534
9842
  };
9535
9843
  }
9536
9844
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
9537
- return toStepResult(await runFfmpeg4(args, { signal, timeout: timeoutMs }), timeoutMs);
9845
+ return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
9538
9846
  }
9539
9847
  function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
9540
9848
  return async (inputPath, outputPath, durationSeconds, filter) => {
@@ -9604,7 +9912,7 @@ function assertAudioNotInterrupted(failures) {
9604
9912
  }
9605
9913
  async function runAudioStage(input) {
9606
9914
  const stageStart = Date.now();
9607
- const pcmSourcePath = join19(input.workDir, "audio-source.wav");
9915
+ const pcmSourcePath = join20(input.workDir, "audio-source.wav");
9608
9916
  const duration = input.totalFrames * input.fps.den / input.fps.num;
9609
9917
  try {
9610
9918
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
@@ -9624,7 +9932,7 @@ async function runAudioStage(input) {
9624
9932
  }
9625
9933
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9626
9934
  } finally {
9627
- rmSync9(pcmSourcePath, { force: true });
9935
+ rmSync10(pcmSourcePath, { force: true });
9628
9936
  }
9629
9937
  }
9630
9938
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -9642,7 +9950,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9642
9950
  assertNotAborted,
9643
9951
  log
9644
9952
  } = input;
9645
- const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
9953
+ const audioOutputPath = join20(workDir, MIXED_AUDIO_FILENAME);
9646
9954
  let hasAudio = false;
9647
9955
  let audioError;
9648
9956
  let audioFailures;
@@ -9653,7 +9961,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9653
9961
  audioResult = await processCompositionAudio(
9654
9962
  audios,
9655
9963
  projectDir,
9656
- join19(workDir, "audio-work"),
9964
+ join20(workDir, "audio-work"),
9657
9965
  audioOutputPath,
9658
9966
  duration,
9659
9967
  abortSignal,
@@ -9722,7 +10030,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9722
10030
 
9723
10031
  // src/services/render/stages/captureStage.ts
9724
10032
  import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
9725
- import { join as join20 } from "node:path";
10033
+ import { join as join21 } from "node:path";
9726
10034
  import {
9727
10035
  classifyCaptureFailure as classifyCaptureFailure2,
9728
10036
  frameFileExtension,
@@ -9795,13 +10103,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
9795
10103
  if (!existsSync12(workDir)) return total;
9796
10104
  for (const attempt of readdirSync3(workDir)) {
9797
10105
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
9798
- total += sampleDirectoryBytes(join20(workDir, attempt));
10106
+ total += sampleDirectoryBytes(join21(workDir, attempt));
9799
10107
  continue;
9800
10108
  }
9801
10109
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
9802
- const attemptDir = join20(workDir, attempt);
10110
+ const attemptDir = join21(workDir, attempt);
9803
10111
  for (const name of readdirSync3(attemptDir)) {
9804
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
10112
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join21(attemptDir, name));
9805
10113
  }
9806
10114
  }
9807
10115
  return total;
@@ -10094,8 +10402,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
10094
10402
  }
10095
10403
 
10096
10404
  // src/services/render/stages/captureHdrStage.ts
10097
- import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
10098
- import { join as join24 } from "node:path";
10405
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
10406
+ import { join as join25 } from "node:path";
10099
10407
  import {
10100
10408
  closeCaptureSession as closeCaptureSession7,
10101
10409
  cloneCaptureWarnings as cloneCaptureWarnings2,
@@ -10167,7 +10475,7 @@ function createHdrImageTransferCache(options = {}) {
10167
10475
  }
10168
10476
 
10169
10477
  // src/services/render/stages/captureHdrSequentialLoop.ts
10170
- import { join as join21 } from "node:path";
10478
+ import { join as join22 } from "node:path";
10171
10479
  import {
10172
10480
  TRANSITIONS,
10173
10481
  crossfade
@@ -10292,7 +10600,7 @@ async function runSequentialLayeredFrameLoop(input) {
10292
10600
  );
10293
10601
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10294
10602
  writeFileExclusiveSync(
10295
- join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10603
+ join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10296
10604
  normalCanvas
10297
10605
  );
10298
10606
  }
@@ -10324,7 +10632,7 @@ async function runSequentialLayeredFrameLoop(input) {
10324
10632
  }
10325
10633
 
10326
10634
  // src/services/render/stages/captureHdrHybridLoop.ts
10327
- import { join as join23 } from "node:path";
10635
+ import { join as join24 } from "node:path";
10328
10636
  import {
10329
10637
  TRANSITIONS as TRANSITIONS2,
10330
10638
  closeCaptureSession as closeCaptureSession6,
@@ -10338,7 +10646,7 @@ import {
10338
10646
  // src/services/shaderTransitionWorkerPool.ts
10339
10647
  import { Worker } from "node:worker_threads";
10340
10648
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
10341
- import { dirname as dirname8, join as join22 } from "node:path";
10649
+ import { dirname as dirname9, join as join23 } from "node:path";
10342
10650
  import { createRequire } from "node:module";
10343
10651
  import { existsSync as existsSync13 } from "node:fs";
10344
10652
  import { cpus as cpus2 } from "node:os";
@@ -10351,10 +10659,10 @@ function resolveWorkerEntry(explicit) {
10351
10659
  const isTs = override.endsWith(".ts");
10352
10660
  return { path: override, isTs };
10353
10661
  }
10354
- const moduleDir = dirname8(fileURLToPath3(import.meta.url));
10355
- const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
10662
+ const moduleDir = dirname9(fileURLToPath3(import.meta.url));
10663
+ const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
10356
10664
  if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
10357
- const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
10665
+ const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
10358
10666
  return { path: tsPath, isTs: true };
10359
10667
  }
10360
10668
  function buildExecArgv(entryIsTs) {
@@ -10497,8 +10805,8 @@ async function createShaderTransitionWorkerPool(opts) {
10497
10805
  if (terminated) {
10498
10806
  throw new Error("shader-blend pool already terminated");
10499
10807
  }
10500
- return new Promise((resolve10, reject) => {
10501
- const task = traceEnabled ? { req, resolve: resolve10, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve10, reject };
10808
+ return new Promise((resolve11, reject) => {
10809
+ const task = traceEnabled ? { req, resolve: resolve11, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve11, reject };
10502
10810
  const idle = slots.find((s) => !s.busy && !s.dead);
10503
10811
  if (idle) {
10504
10812
  queue.unshift(task);
@@ -10722,7 +11030,7 @@ async function runHybridLayeredFrameLoop(input) {
10722
11030
  );
10723
11031
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10724
11032
  writeFileExclusiveSync(
10725
- join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11033
+ join24(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10726
11034
  canvas
10727
11035
  );
10728
11036
  }
@@ -10910,9 +11218,9 @@ async function runCaptureHdrStage(input) {
10910
11218
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
10911
11219
  }
10912
11220
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
10913
- const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
11221
+ const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
10914
11222
  if (debugDumpDir && !existsSync14(debugDumpDir)) {
10915
- mkdirSync7(debugDumpDir, { recursive: true });
11223
+ mkdirSync8(debugDumpDir, { recursive: true });
10916
11224
  }
10917
11225
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
10918
11226
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -11065,24 +11373,24 @@ async function runCaptureHdrStage(input) {
11065
11373
 
11066
11374
  // src/services/render/stages/encodeStage.ts
11067
11375
  import {
11068
- copyFileSync as copyFileSync3,
11376
+ copyFileSync as copyFileSync4,
11069
11377
  existsSync as existsSync15,
11070
- mkdirSync as mkdirSync8,
11378
+ mkdirSync as mkdirSync9,
11071
11379
  readdirSync as readdirSync4,
11072
- readFileSync as readFileSync7,
11073
- rmSync as rmSync10,
11074
- statSync as statSync6,
11075
- writeFileSync as writeFileSync4
11380
+ readFileSync as readFileSync8,
11381
+ rmSync as rmSync11,
11382
+ statSync as statSync7,
11383
+ writeFileSync as writeFileSync5
11076
11384
  } from "node:fs";
11077
11385
  import { readFile as readFile2 } from "node:fs/promises";
11078
- import { dirname as dirname9, join as join25 } from "node:path";
11386
+ import { dirname as dirname10, join as join26 } from "node:path";
11079
11387
  import {
11080
11388
  encodeFramesChunkedConcat,
11081
11389
  encodeFramesFromDir,
11082
11390
  formatFfmpegError,
11083
11391
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
11084
11392
  resolveConfig,
11085
- runFfmpeg as runFfmpeg5,
11393
+ runFfmpeg as runFfmpeg6,
11086
11394
  spawnStreamingEncoder as spawnStreamingEncoder4
11087
11395
  } from "@hyperframes/engine";
11088
11396
  import {
@@ -11158,17 +11466,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
11158
11466
  }
11159
11467
  async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
11160
11468
  const { outputPath } = argsInput;
11161
- const result = await runFfmpeg5(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11469
+ const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11162
11470
  if (!result.success) {
11163
11471
  const error = formatFfmpegError(result.exitCode, result.stderr);
11164
11472
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11165
11473
  }
11166
- const gif = readFileSync7(outputPath);
11474
+ const gif = readFileSync8(outputPath);
11167
11475
  if (!clearGifFramesBeforeNext(gif)) {
11168
11476
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
11169
11477
  return gifEncodeFailure(startTime, outputPath, error);
11170
11478
  }
11171
- writeFileSync4(outputPath, gif);
11479
+ writeFileSync5(outputPath, gif);
11172
11480
  return null;
11173
11481
  }
11174
11482
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -11190,17 +11498,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11190
11498
  try {
11191
11499
  const run = { signal: input.signal, timeout: input.timeout };
11192
11500
  for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
11193
- const result = await runFfmpeg5(args, run);
11501
+ const result = await runFfmpeg6(args, run);
11194
11502
  if (!result.success) {
11195
11503
  const error = formatFfmpegError(result.exitCode, result.stderr);
11196
11504
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11197
11505
  }
11198
11506
  }
11199
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
11507
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
11200
11508
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
11201
11509
  if (failure) return failure;
11202
11510
  }
11203
- const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
11511
+ const fileSize = existsSync15(outputPath) ? statSync7(outputPath).size : 0;
11204
11512
  return {
11205
11513
  success: true,
11206
11514
  outputPath,
@@ -11209,7 +11517,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11209
11517
  fileSize
11210
11518
  };
11211
11519
  } finally {
11212
- rmSync10(input.palettePath, { force: true });
11520
+ rmSync11(input.palettePath, { force: true });
11213
11521
  }
11214
11522
  }
11215
11523
  function startEncodeProgress(job, stage, onProgress) {
@@ -11281,10 +11589,10 @@ function encodeWhileCapturing(input, totalFrames) {
11281
11589
  const stream = await encoder;
11282
11590
  for (let next = 0; next < totalFrames; next++) {
11283
11591
  while (!landed.has(next) && !captureDone && !signal.aborted) {
11284
- await new Promise((resolve10) => wake = resolve10);
11592
+ await new Promise((resolve11) => wake = resolve11);
11285
11593
  }
11286
11594
  if (signal.aborted) return;
11287
- const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
11595
+ const mergedPath = join26(input.framesDir, formatCaptureFrameName(next, "jpg"));
11288
11596
  const frame = await readLandedFrame(landed.get(next), mergedPath);
11289
11597
  landed.delete(next);
11290
11598
  if (!await stream.writeFrame(frame)) {
@@ -11359,7 +11667,7 @@ async function runEncodeStage(input) {
11359
11667
  const stage5Start = Date.now();
11360
11668
  if (isPngSequence) {
11361
11669
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
11362
- if (!existsSync15(outputPath)) mkdirSync8(outputPath, { recursive: true });
11670
+ if (!existsSync15(outputPath)) mkdirSync9(outputPath, { recursive: true });
11363
11671
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
11364
11672
  if (captured.length === 0) {
11365
11673
  throw new Error(
@@ -11367,11 +11675,11 @@ async function runEncodeStage(input) {
11367
11675
  );
11368
11676
  }
11369
11677
  captured.forEach((name, i) => {
11370
- const dst = join25(outputPath, formatExportFrameName(i, "png"));
11371
- copyFileSync3(join25(framesDir, name), dst);
11678
+ const dst = join26(outputPath, formatExportFrameName(i, "png"));
11679
+ copyFileSync4(join26(framesDir, name), dst);
11372
11680
  });
11373
11681
  if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
11374
- copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
11682
+ copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
11375
11683
  log.info(
11376
11684
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
11377
11685
  );
@@ -11391,7 +11699,7 @@ async function runEncodeStage(input) {
11391
11699
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
11392
11700
  fps: job.config.fps,
11393
11701
  loop,
11394
- palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
11702
+ palettePath: join26(dirname10(videoOnlyPath), "gif-palette.png"),
11395
11703
  preserveAlpha: needsAlpha,
11396
11704
  signal: abortSignal,
11397
11705
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -11601,9 +11909,9 @@ function sampleDirectoryBytes(dir) {
11601
11909
  continue;
11602
11910
  }
11603
11911
  for (const name of entries) {
11604
- const full = join26(current, name);
11912
+ const full = join27(current, name);
11605
11913
  try {
11606
- const st = statSync7(full);
11914
+ const st = statSync8(full);
11607
11915
  if (st.isDirectory()) {
11608
11916
  stack.push(full);
11609
11917
  } else if (st.isFile()) {
@@ -11750,8 +12058,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11750
12058
  const ranges = [];
11751
12059
  let rangeStart = null;
11752
12060
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11753
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11754
- const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
12061
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12062
+ const missing = !existsSync16(framePath) || statSync8(framePath).size <= 8;
11755
12063
  if (missing && rangeStart === null) {
11756
12064
  rangeStart = frameIndex;
11757
12065
  } else if (!missing && rangeStart !== null) {
@@ -11767,7 +12075,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11767
12075
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
11768
12076
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
11769
12077
  const batches = [];
11770
- const outputDirFor = (batchIndex, workerId) => join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
12078
+ const outputDirFor = (batchIndex, workerId) => join27(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
11771
12079
  if (stride > 1) {
11772
12080
  const spans = /* @__PURE__ */ new Map();
11773
12081
  for (const range of ranges) {
@@ -11827,8 +12135,8 @@ function getNextRetryWorkerCount(currentWorkers) {
11827
12135
  return Math.max(1, Math.floor(currentWorkers / 2));
11828
12136
  }
11829
12137
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
11830
- if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
11831
- return join26(dirname10(outputPath), `work-${jobId}-`);
12138
+ if (platform2 === "win32" || platform2 === "darwin") return join27(systemTempDir, "hf-render-");
12139
+ return join27(dirname11(outputPath), `work-${jobId}-`);
11832
12140
  }
11833
12141
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
11834
12142
  function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
@@ -11870,7 +12178,7 @@ The composition is too large for the available memory. To reduce memory pressure
11870
12178
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
11871
12179
  let captured = 0;
11872
12180
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11873
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12181
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11874
12182
  if (existsSync16(framePath)) captured++;
11875
12183
  }
11876
12184
  return captured;
@@ -11896,7 +12204,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11896
12204
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
11897
12205
  });
11898
12206
  pendingTransientRetry = false;
11899
- const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
12207
+ const attemptWorkDir = join27(options.workDir, `capture-attempt-${attempt}`);
11900
12208
  const batches = missingRanges ? buildMissingFrameRetryBatches(
11901
12209
  missingRanges,
11902
12210
  currentWorkers,
@@ -12502,18 +12810,18 @@ function deVerifyFallbackTelemetry(err) {
12502
12810
  };
12503
12811
  }
12504
12812
  function resolveRenderDebugDir() {
12505
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
12506
- return join26(producerRoot, ".debug");
12813
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname11(fileURLToPath4(import.meta.url)), "../..");
12814
+ return join27(producerRoot, ".debug");
12507
12815
  }
12508
12816
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
12509
12817
  validateHlsRenderConfig(job.config);
12510
12818
  const debugDir = resolveRenderDebugDir();
12511
- const outputDir = dirname10(outputPath);
12512
- if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
12513
- const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12819
+ const outputDir = dirname11(outputPath);
12820
+ if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
12821
+ const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12514
12822
  const pipelineStart = Date.now();
12515
12823
  const baseLog = job.config.logger ?? defaultLogger;
12516
- const logPath = job.config.debug ? join26(workDir, "render.log") : null;
12824
+ const logPath = job.config.debug ? join27(workDir, "render.log") : null;
12517
12825
  const execution = new RenderExecutionContext({
12518
12826
  request: { renderJobId: job.id, projectDir, outputPath },
12519
12827
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -12527,7 +12835,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
12527
12835
  log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
12528
12836
  return;
12529
12837
  }
12530
- rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12838
+ rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12531
12839
  });
12532
12840
  try {
12533
12841
  await executeRenderPipeline({
@@ -12573,7 +12881,7 @@ async function executeRenderPipeline(input) {
12573
12881
  imageDecodeFailures: 0
12574
12882
  };
12575
12883
  let hdrPerf;
12576
- const perfOutputPath = join26(workDir, "perf-summary.json");
12884
+ const perfOutputPath = join27(workDir, "perf-summary.json");
12577
12885
  const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
12578
12886
  const observability = new RenderObservabilityRecorder({
12579
12887
  pipelineStartMs: pipelineStart,
@@ -12646,7 +12954,7 @@ async function executeRenderPipeline(input) {
12646
12954
  job.startedAt = /* @__PURE__ */ new Date();
12647
12955
  assertNotAborted();
12648
12956
  assertConfiguredFfmpegBinariesExist();
12649
- if (!existsSync16(workDir)) mkdirSync9(workDir, { recursive: true });
12957
+ if (!existsSync16(workDir)) mkdirSync10(workDir, { recursive: true });
12650
12958
  if (job.config.debug) {
12651
12959
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
12652
12960
  }
@@ -12675,22 +12983,22 @@ async function executeRenderPipeline(input) {
12675
12983
  requestedWorkers: job.config.workers ?? "auto"
12676
12984
  });
12677
12985
  const entryFile = job.config.entryFile || "index.html";
12678
- let htmlPath = join26(projectDir, entryFile);
12986
+ let htmlPath = join27(projectDir, entryFile);
12679
12987
  if (!existsSync16(htmlPath)) {
12680
12988
  throw new Error(`Entry file not found: ${htmlPath}`);
12681
12989
  }
12682
12990
  assertNotAborted();
12683
- const rawEntry = readFileSync8(htmlPath, "utf-8");
12991
+ const rawEntry = readFileSync9(htmlPath, "utf-8");
12684
12992
  if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
12685
- const wrapperPath = join26(workDir, "standalone-entry.html");
12686
- const projectIndexPath = join26(projectDir, "index.html");
12993
+ const wrapperPath = join27(workDir, "standalone-entry.html");
12994
+ const projectIndexPath = join27(projectDir, "index.html");
12687
12995
  if (!existsSync16(projectIndexPath)) {
12688
12996
  throw new Error(
12689
12997
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
12690
12998
  );
12691
12999
  }
12692
13000
  const standaloneHtml = extractStandaloneEntryFromIndex(
12693
- readFileSync8(projectIndexPath, "utf-8"),
13001
+ readFileSync9(projectIndexPath, "utf-8"),
12694
13002
  entryFile,
12695
13003
  rawEntry
12696
13004
  );
@@ -12699,7 +13007,7 @@ async function executeRenderPipeline(input) {
12699
13007
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
12700
13008
  );
12701
13009
  }
12702
- writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
13010
+ writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
12703
13011
  htmlPath = wrapperPath;
12704
13012
  log.info("Extracted standalone entry from index.html host context", {
12705
13013
  entryFile
@@ -12846,7 +13154,7 @@ async function executeRenderPipeline(input) {
12846
13154
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
12847
13155
  code: "extract_video"
12848
13156
  });
12849
- const compiledDir = join26(workDir, "compiled");
13157
+ const compiledDir = join27(workDir, "compiled");
12850
13158
  const extractResult = await observeRenderStage(
12851
13159
  observability,
12852
13160
  "video_extract",
@@ -12980,7 +13288,7 @@ async function executeRenderPipeline(input) {
12980
13288
  try {
12981
13289
  fileServer = await createFileServer({
12982
13290
  projectDir,
12983
- compiledDir: join26(workDir, "compiled"),
13291
+ compiledDir: join27(workDir, "compiled"),
12984
13292
  port: 0,
12985
13293
  preHeadScripts: [VIRTUAL_TIME_SHIM],
12986
13294
  fps: job.config.fps
@@ -12998,8 +13306,8 @@ async function executeRenderPipeline(input) {
12998
13306
  if (!activeFileServer) {
12999
13307
  throw new Error("File server failed to initialize before frame capture");
13000
13308
  }
13001
- const framesDir = join26(workDir, "captured-frames");
13002
- if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
13309
+ const framesDir = join27(workDir, "captured-frames");
13310
+ if (!existsSync16(framesDir)) mkdirSync10(framesDir, { recursive: true });
13003
13311
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
13004
13312
  code: "start_browsers"
13005
13313
  });
@@ -13470,7 +13778,7 @@ async function executeRenderPipeline(input) {
13470
13778
  hls: ".mp4"
13471
13779
  };
13472
13780
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
13473
- const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
13781
+ const videoOnlyPath = join27(workDir, `video-only${videoExt}`);
13474
13782
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
13475
13783
  if (usePageSideCompositingForTransitions) {
13476
13784
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -13723,7 +14031,7 @@ async function executeRenderPipeline(input) {
13723
14031
  pageMotionBlur: job.config.pageMotionBlur,
13724
14032
  captureQuality: captureOptions.quality
13725
14033
  });
13726
- const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
14034
+ const segmentDir = segmentDirFor(join27(projectDir, "renders"), segmentPlanHash);
13727
14035
  let completedSegments = /* @__PURE__ */ new Set();
13728
14036
  if (job.config.resumeSegments === true) {
13729
14037
  const existing = readSegmentManifest(segmentDir);
@@ -13738,10 +14046,10 @@ async function executeRenderPipeline(input) {
13738
14046
  });
13739
14047
  }
13740
14048
  if (completedSegments.size === 0) {
13741
- rmSync11(segmentDir, { recursive: true, force: true });
14049
+ rmSync12(segmentDir, { recursive: true, force: true });
13742
14050
  }
13743
14051
  } else {
13744
- rmSync11(segmentDir, { recursive: true, force: true });
14052
+ rmSync12(segmentDir, { recursive: true, force: true });
13745
14053
  }
13746
14054
  const segmentManifest = readSegmentManifest(segmentDir) ?? {
13747
14055
  version: 1,
@@ -13822,7 +14130,7 @@ async function executeRenderPipeline(input) {
13822
14130
  );
13823
14131
  updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
13824
14132
  if (job.config.keepSegments !== true) {
13825
- rmSync11(segmentDir, { recursive: true, force: true });
14133
+ rmSync12(segmentDir, { recursive: true, force: true });
13826
14134
  }
13827
14135
  } else {
13828
14136
  capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
@@ -14094,8 +14402,8 @@ async function executeRenderPipeline(input) {
14094
14402
  "capture_disk",
14095
14403
  "drawElement capture failed; retrying with a fresh screenshot session"
14096
14404
  );
14097
- rmSync11(framesDir, { recursive: true, force: true });
14098
- mkdirSync9(framesDir, { recursive: true });
14405
+ rmSync12(framesDir, { recursive: true, force: true });
14406
+ mkdirSync10(framesDir, { recursive: true });
14099
14407
  resetCaptureAttemptProgress(job);
14100
14408
  dedupPerfs.length = 0;
14101
14409
  cfg.useDrawElement = false;
@@ -14271,7 +14579,7 @@ async function executeRenderPipeline(input) {
14271
14579
  job.perfSummary = perfSummary;
14272
14580
  if (job.config.debug) {
14273
14581
  try {
14274
- writeFileSync5(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14582
+ writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14275
14583
  } catch (err) {
14276
14584
  log.debug("Failed to write perf summary", {
14277
14585
  perfOutputPath,
@@ -14281,8 +14589,8 @@ async function executeRenderPipeline(input) {
14281
14589
  }
14282
14590
  if (job.config.debug) {
14283
14591
  if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
14284
- const debugOutput = join26(workDir, `output${videoExt}`);
14285
- copyFileSync4(stagedOutputPath, debugOutput);
14592
+ const debugOutput = join27(workDir, `output${videoExt}`);
14593
+ copyFileSync5(stagedOutputPath, debugOutput);
14286
14594
  }
14287
14595
  }
14288
14596
  await commitArtifactTransaction(artifactTransaction, () => {
@@ -14387,14 +14695,14 @@ async function executeRenderPipeline(input) {
14387
14695
  // src/services/hyperframeLint.ts
14388
14696
  import {
14389
14697
  existsSync as existsSync17,
14390
- readFileSync as readFileSync9,
14391
- statSync as statSync8,
14392
- openSync as openSync4,
14698
+ readFileSync as readFileSync10,
14699
+ statSync as statSync9,
14700
+ openSync as openSync5,
14393
14701
  fstatSync as fstatSync3,
14394
- closeSync as closeSync4,
14702
+ closeSync as closeSync5,
14395
14703
  constants as constants3
14396
14704
  } from "node:fs";
14397
- import { resolve as resolve8, join as join27, relative as relative3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
14705
+ import { resolve as resolve9, join as join28, relative as relative3, isAbsolute as isAbsolute3, sep as sep2 } from "node:path";
14398
14706
  import { lintHyperframeHtml } from "@hyperframes/lint";
14399
14707
  function isStringRecord(value) {
14400
14708
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -14424,30 +14732,30 @@ function pickEntryFile(files, preferredEntryFile) {
14424
14732
  function readEntryHtml(filePath) {
14425
14733
  let fd;
14426
14734
  try {
14427
- fd = openSync4(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
14735
+ fd = openSync5(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
14428
14736
  } catch (error) {
14429
14737
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
14430
14738
  return null;
14431
- if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
14739
+ if (!statSync9(filePath, { throwIfNoEntry: false })?.isFile()) return null;
14432
14740
  throw error;
14433
14741
  }
14434
14742
  try {
14435
14743
  if (!fstatSync3(fd).isFile()) return null;
14436
- return readFileSync9(fd, "utf-8");
14744
+ return readFileSync10(fd, "utf-8");
14437
14745
  } finally {
14438
- closeSync4(fd);
14746
+ closeSync5(fd);
14439
14747
  }
14440
14748
  }
14441
14749
  function readProjectEntryFile(projectDir, preferredEntryFile) {
14442
- const absProjectDir = resolve8(projectDir);
14443
- if (!existsSync17(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
14750
+ const absProjectDir = resolve9(projectDir);
14751
+ if (!existsSync17(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
14444
14752
  return { error: `Project directory not found: ${absProjectDir}` };
14445
14753
  }
14446
14754
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
14447
14755
  (value) => typeof value === "string" && value.trim().length > 0
14448
14756
  );
14449
14757
  for (const entryFile of entryCandidates) {
14450
- const absoluteEntryPath = resolve8(absProjectDir, entryFile);
14758
+ const absoluteEntryPath = resolve9(absProjectDir, entryFile);
14451
14759
  const entryRelativePath = relative3(absProjectDir, absoluteEntryPath);
14452
14760
  if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep2}`) || isAbsolute3(entryRelativePath)) {
14453
14761
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
@@ -14462,7 +14770,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
14462
14770
  }
14463
14771
  }
14464
14772
  return {
14465
- error: `No HTML entry file found in project directory: ${join27(absProjectDir, preferredEntryFile || "index.html")}`
14773
+ error: `No HTML entry file found in project directory: ${join28(absProjectDir, preferredEntryFile || "index.html")}`
14466
14774
  };
14467
14775
  }
14468
14776
  function prepareHyperframeLintBody(body) {
@@ -14508,7 +14816,7 @@ async function runHyperframeLint(prepared) {
14508
14816
  // src/services/healthWorker.ts
14509
14817
  import { Worker as Worker2 } from "node:worker_threads";
14510
14818
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14511
- import { dirname as dirname11, join as join28 } from "node:path";
14819
+ import { dirname as dirname12, join as join29 } from "node:path";
14512
14820
  import { existsSync as existsSync18 } from "node:fs";
14513
14821
  var DEFAULT_HEALTH_PORT = 9848;
14514
14822
  async function startHealthWorker(options = {}) {
@@ -14527,12 +14835,12 @@ async function startHealthWorker(options = {}) {
14527
14835
  stdout: false,
14528
14836
  stderr: false
14529
14837
  });
14530
- await new Promise((resolve10, reject) => {
14838
+ await new Promise((resolve11, reject) => {
14531
14839
  const onMessage = (msg) => {
14532
14840
  if (msg?.type === "listening") {
14533
14841
  worker.off("error", onError);
14534
14842
  worker.off("message", onMessage);
14535
- resolve10();
14843
+ resolve11();
14536
14844
  } else if (msg?.type === "listen-error") {
14537
14845
  worker.off("error", onError);
14538
14846
  worker.off("message", onMessage);
@@ -14583,8 +14891,8 @@ function defaultLogger2() {
14583
14891
  };
14584
14892
  }
14585
14893
  function resolveWorkerEntry2() {
14586
- const here = dirname11(fileURLToPath5(import.meta.url));
14587
- const candidates = [join28(here, "healthWorkerThread.js"), join28(here, "healthWorkerThread.ts")];
14894
+ const here = dirname12(fileURLToPath5(import.meta.url));
14895
+ const candidates = [join29(here, "healthWorkerThread.js"), join29(here, "healthWorkerThread.ts")];
14588
14896
  for (const candidate of candidates) {
14589
14897
  if (existsSync18(candidate)) return candidate;
14590
14898
  }
@@ -14620,7 +14928,7 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
14620
14928
 
14621
14929
  // src/services/distributed/plan.ts
14622
14930
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
14623
- import { extname as extname4, isAbsolute as isAbsolute4, join as join29, relative as relative4, sep as sep3 } from "node:path";
14931
+ import { extname as extname4, isAbsolute as isAbsolute4, join as join30, relative as relative4, sep as sep3 } from "node:path";
14624
14932
  import {
14625
14933
  fpsToNumber as fpsToNumber10,
14626
14934
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -14630,7 +14938,7 @@ import {
14630
14938
  normalizeVp9CpuUsed,
14631
14939
  resolveConfig as resolveConfig3,
14632
14940
  declareContentAddressedUrl,
14633
- renameWithRetry as renameWithRetry6,
14941
+ renameWithRetry as renameWithRetry7,
14634
14942
  declareProbeFactsUrl
14635
14943
  } from "@hyperframes/engine";
14636
14944
 
@@ -14701,10 +15009,10 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
14701
15009
  var FIELD_DELIMITER = Buffer.from([0]);
14702
15010
 
14703
15011
  // src/services/distributed/chunkAudio.ts
14704
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
15012
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
14705
15013
 
14706
15014
  // src/services/distributed/mp4Stitch.ts
14707
- import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
15015
+ import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
14708
15016
  var SCAN_BYTES = 64 * 1024;
14709
15017
 
14710
15018
  // src/services/distributed/chunkAudio.ts
@@ -14724,9 +15032,9 @@ var PLAN_ROOT_CATEGORY = {
14724
15032
  var PLAN_DIR_SIZE_LIMIT_BYTES = 2 * 1024 * 1024 * 1024;
14725
15033
  var FREEZE_OWNED_PLAN_FILES = [
14726
15034
  "plan.json",
14727
- join29("meta", "composition.json"),
14728
- join29("meta", "encoder.json"),
14729
- join29("meta", "chunks.json")
15035
+ join30("meta", "composition.json"),
15036
+ join30("meta", "encoder.json"),
15037
+ join30("meta", "chunks.json")
14730
15038
  ];
14731
15039
 
14732
15040
  // src/services/distributed/renderConfigValidation.ts
@@ -14957,6 +15265,8 @@ var DEFAULT_SERVER_FPS = { num: 30, den: 1 };
14957
15265
  var SSE_KEEPALIVE_MS = 3e4;
14958
15266
  var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
14959
15267
  "ASSET_MEDIA_TYPE_MISMATCH",
15268
+ "HEIF_IMAGE_CONVERSION_FAILED",
15269
+ "HEIF_TOOL_UNAVAILABLE",
14960
15270
  "NOT_MEDIA_PAYLOAD",
14961
15271
  "INVALID_VIDEO_METADATA",
14962
15272
  "VIDEO_SOURCE_UNRENDERABLE",
@@ -15092,8 +15402,8 @@ function buildRenderJobConfig(input, outputPath, log) {
15092
15402
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
15093
15403
  const { input, cleanupProjectDir } = prepared;
15094
15404
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
15095
- const outputDir = dirname12(absoluteOutputPath);
15096
- if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
15405
+ const outputDir = dirname13(absoluteOutputPath);
15406
+ if (!existsSync19(outputDir)) mkdirSync11(outputDir, { recursive: true });
15097
15407
  return { input, cleanupProjectDir, absoluteOutputPath };
15098
15408
  }
15099
15409
  function validateRenderOverrides(body) {
@@ -15133,12 +15443,12 @@ var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
15133
15443
  function prepareProjectDirectory(projectDir, options) {
15134
15444
  const candidate = nonEmptyString(projectDir);
15135
15445
  if (!candidate) return null;
15136
- const absProjectDir = resolve9(candidate);
15137
- if (!existsSync19(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
15446
+ const absProjectDir = resolve10(candidate);
15447
+ if (!existsSync19(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
15138
15448
  return { error: `Project directory not found: ${absProjectDir}` };
15139
15449
  }
15140
15450
  const entry = options.entryFile || "index.html";
15141
- if (!existsSync19(resolve9(absProjectDir, entry))) {
15451
+ if (!existsSync19(resolve10(absProjectDir, entry))) {
15142
15452
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
15143
15453
  }
15144
15454
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -15159,8 +15469,8 @@ async function resolveInlineRenderHtml(body) {
15159
15469
  }
15160
15470
  function materializeInlineProject(html, options) {
15161
15471
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
15162
- const tempProjectDir = mkdtempSync4(join30(tempRoot, "producer-project-"));
15163
- writeFileSync6(join30(tempProjectDir, "index.html"), html, "utf-8");
15472
+ const tempProjectDir = mkdtempSync4(join31(tempRoot, "producer-project-"));
15473
+ writeFileSync7(join31(tempProjectDir, "index.html"), html, "utf-8");
15164
15474
  return {
15165
15475
  prepared: {
15166
15476
  input: { projectDir: tempProjectDir, ...options },
@@ -15185,7 +15495,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
15185
15495
  try {
15186
15496
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
15187
15497
  } catch (error) {
15188
- const fallbackPath = resolve9(rendersDir, `producer-fallback-${Date.now()}.mp4`);
15498
+ const fallbackPath = resolve10(rendersDir, `producer-fallback-${Date.now()}.mp4`);
15189
15499
  log.warn("Failed to resolve output path, using fallback", {
15190
15500
  fallback: fallbackPath,
15191
15501
  error: error instanceof Error ? error.message : String(error)
@@ -15221,7 +15531,7 @@ function createArtifactStore(ttlMs) {
15221
15531
  function cleanupTempDir(dir, log) {
15222
15532
  if (!dir) return;
15223
15533
  try {
15224
- rmSync12(dir, { recursive: true, force: true });
15534
+ rmSync13(dir, { recursive: true, force: true });
15225
15535
  } catch (error) {
15226
15536
  log.warn("Failed to cleanup temp project dir", {
15227
15537
  cleanupProjectDir: dir,
@@ -15230,7 +15540,7 @@ function cleanupTempDir(dir, log) {
15230
15540
  }
15231
15541
  }
15232
15542
  function outputFileSize(path) {
15233
- return existsSync19(path) ? statSync9(path).size : 0;
15543
+ return existsSync19(path) ? statSync10(path).size : 0;
15234
15544
  }
15235
15545
  function createBlockingProgressReporter(log, requestId) {
15236
15546
  let lastLoggedPct = -10;
@@ -15536,7 +15846,7 @@ function createRenderHandlers(options = {}) {
15536
15846
  store.delete(token);
15537
15847
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
15538
15848
  }
15539
- const stats = statSync9(artifact.path);
15849
+ const stats = statSync10(artifact.path);
15540
15850
  return new Response(createReadStream3(artifact.path), {
15541
15851
  headers: {
15542
15852
  "content-type": "video/mp4",
@@ -15603,7 +15913,7 @@ function startServer(options = {}) {
15603
15913
  process.on("SIGINT", () => shutdown("SIGINT"));
15604
15914
  return server;
15605
15915
  }
15606
- var entryScript = process.argv[1] ? resolve9(process.argv[1]) : "";
15916
+ var entryScript = process.argv[1] ? resolve10(process.argv[1]) : "";
15607
15917
  var isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
15608
15918
  if (isPublicServerEntry) {
15609
15919
  const { values } = parseArgs({