hyperframes 0.8.26 → 0.8.28

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.
@@ -3433,10 +3433,10 @@ function compareDocumentPosition(nodeA, nodeB) {
3433
3433
  function uniqueSort(nodes) {
3434
3434
  nodes = nodes.filter((node, i, arr) => !arr.includes(node, i + 1));
3435
3435
  nodes.sort((a, b) => {
3436
- const relative7 = compareDocumentPosition(a, b);
3437
- if (relative7 & DocumentPosition.PRECEDING) {
3436
+ const relative8 = compareDocumentPosition(a, b);
3437
+ if (relative8 & DocumentPosition.PRECEDING) {
3438
3438
  return -1;
3439
- } else if (relative7 & DocumentPosition.FOLLOWING) {
3439
+ } else if (relative8 & DocumentPosition.FOLLOWING) {
3440
3440
  return 1;
3441
3441
  }
3442
3442
  return 0;
@@ -5771,10 +5771,10 @@ function parse2(formula) {
5771
5771
  if (idx < formula.length && formula.charAt(idx) === "n") {
5772
5772
  idx++;
5773
5773
  a = sign * (number !== null && number !== void 0 ? number : 1);
5774
- skipWhitespace();
5774
+ skipWhitespace2();
5775
5775
  if (idx < formula.length) {
5776
5776
  sign = readSign();
5777
- skipWhitespace();
5777
+ skipWhitespace2();
5778
5778
  number = readNumber();
5779
5779
  } else {
5780
5780
  sign = number = 0;
@@ -5803,7 +5803,7 @@ function parse2(formula) {
5803
5803
  }
5804
5804
  return idx === start ? null : value;
5805
5805
  }
5806
- function skipWhitespace() {
5806
+ function skipWhitespace2() {
5807
5807
  while (idx < formula.length && whitespace.has(formula.charCodeAt(idx))) {
5808
5808
  idx++;
5809
5809
  }
@@ -17022,7 +17022,7 @@ var require_util = __commonJS({
17022
17022
  return path;
17023
17023
  }
17024
17024
  exports.normalize = normalize;
17025
- function join56(aRoot, aPath) {
17025
+ function join57(aRoot, aPath) {
17026
17026
  if (aRoot === "") {
17027
17027
  aRoot = ".";
17028
17028
  }
@@ -17054,11 +17054,11 @@ var require_util = __commonJS({
17054
17054
  }
17055
17055
  return joined;
17056
17056
  }
17057
- exports.join = join56;
17057
+ exports.join = join57;
17058
17058
  exports.isAbsolute = function(aPath) {
17059
17059
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
17060
17060
  };
17061
- function relative7(aRoot, aPath) {
17061
+ function relative8(aRoot, aPath) {
17062
17062
  if (aRoot === "") {
17063
17063
  aRoot = ".";
17064
17064
  }
@@ -17077,7 +17077,7 @@ var require_util = __commonJS({
17077
17077
  }
17078
17078
  return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
17079
17079
  }
17080
- exports.relative = relative7;
17080
+ exports.relative = relative8;
17081
17081
  var supportsNullProto = (function() {
17082
17082
  var obj = /* @__PURE__ */ Object.create(null);
17083
17083
  return !("__proto__" in obj);
@@ -17227,7 +17227,7 @@ var require_util = __commonJS({
17227
17227
  parsed.path = parsed.path.substring(0, index + 1);
17228
17228
  }
17229
17229
  }
17230
- sourceURL = join56(urlGenerate(parsed), sourceURL);
17230
+ sourceURL = join57(urlGenerate(parsed), sourceURL);
17231
17231
  }
17232
17232
  return normalize(sourceURL);
17233
17233
  }
@@ -50174,13 +50174,6 @@ function checkOutputResolutionCompatibility(input) {
50174
50174
  message: `outputResolution cannot be combined with hdrMode='force-hdr'. HDR rendering composites at composition dimensions and does not yet support supersampling. Pick one or render in two passes.`
50175
50175
  };
50176
50176
  }
50177
- if (input.alphaRequested) {
50178
- return {
50179
- ok: false,
50180
- kind: "alpha-incompatible",
50181
- message: `outputResolution cannot be combined with alpha output (--format webm|mov|png-sequence). The alpha screenshot path does not yet apply deviceScaleFactor and would silently produce composition-resolution frames. Render alpha at composition resolution and upscale separately, or use --format mp4.`
50182
- };
50183
- }
50184
50177
  const target = CANVAS_DIMENSIONS[outputResolution];
50185
50178
  if (target.width * compositionHeight !== target.height * compositionWidth) {
50186
50179
  return buildAspectMismatch(compositionWidth, compositionHeight, outputResolution, target);
@@ -52567,7 +52560,7 @@ var init_telemetryRedaction = __esm({
52567
52560
 
52568
52561
  // ../core/dist/safePath.js
52569
52562
  import { resolve as resolve3, sep, join as join2, dirname as dirname3, basename } from "path";
52570
- import { realpathSync } from "fs";
52563
+ import { lstatSync, realpathSync } from "fs";
52571
52564
  var init_safePath = __esm({
52572
52565
  "../core/dist/safePath.js"() {
52573
52566
  "use strict";
@@ -57686,6 +57679,7 @@ var init_captureFailure = __esm({
57686
57679
  PROTOCOL_TIMEOUT_PATTERNS = [
57687
57680
  /Runtime\.callFunctionOn timed out/i,
57688
57681
  /Runtime\.evaluate timed out/i,
57682
+ /Page\.captureScreenshot timed out/i,
57689
57683
  /HeadlessExperimental\.beginFrame timed out/i,
57690
57684
  /drawElement worker encode timed out \(frame \d+\)/i,
57691
57685
  /Protocol error[\s\S]*tim(?:ed|e) out/i,
@@ -58652,6 +58646,18 @@ function recordScriptLoadFailure(session, url) {
58652
58646
  session.scriptLoadFailures.push(url);
58653
58647
  }
58654
58648
  }
58649
+ function classifyConsoleScriptFailure(type, text) {
58650
+ if (type !== "error") return null;
58651
+ if (text.startsWith("[HyperFrames] composition script error:")) {
58652
+ const detail = text.slice("[HyperFrames] composition script error:".length).trim();
58653
+ const compId = detail.split(" ")[0] || "unknown";
58654
+ return `runtime-error:${compId}`;
58655
+ }
58656
+ if (/failed to find a valid digest in the ['"]integrity['"] attribute/i.test(text) && /resource has been blocked/i.test(text)) {
58657
+ return "runtime-error:subresource-integrity";
58658
+ }
58659
+ return null;
58660
+ }
58655
58661
  async function initializeSession(session) {
58656
58662
  const { page, serverUrl } = session;
58657
58663
  page.on("console", (msg) => {
@@ -58661,11 +58667,8 @@ async function initializeSession(session) {
58661
58667
  const diagnostic = formatConsoleDiagnostic(type, text, locationUrl);
58662
58668
  if (!diagnostic.suppressHostLog) console.log(diagnostic.text);
58663
58669
  appendBrowserDiagnostic(session, diagnostic.text);
58664
- if (type === "error" && text.startsWith("[HyperFrames] composition script error:")) {
58665
- const detail = text.slice("[HyperFrames] composition script error:".length).trim();
58666
- const compId = detail.split(" ")[0] || "unknown";
58667
- recordScriptLoadFailure(session, `runtime-error:${compId}`);
58668
- }
58670
+ const scriptFailure = classifyConsoleScriptFailure(type, text);
58671
+ if (scriptFailure) recordScriptLoadFailure(session, scriptFailure);
58669
58672
  });
58670
58673
  page.on("pageerror", (err) => {
58671
58674
  const message = err instanceof Error ? err.message : String(err);
@@ -62143,7 +62146,7 @@ import {
62143
62146
  linkSync,
62144
62147
  mkdtempSync,
62145
62148
  mkdirSync as mkdirSync4,
62146
- lstatSync,
62149
+ lstatSync as lstatSync2,
62147
62150
  openSync,
62148
62151
  readdirSync as readdirSync3,
62149
62152
  rmdirSync,
@@ -62257,9 +62260,9 @@ function sameCacheLockStatGeneration(left, right) {
62257
62260
  }
62258
62261
  function observeCachePathLock(lockPath) {
62259
62262
  for (let pass = 0; pass < 3; pass += 1) {
62260
- const before2 = lstatSync(lockPath);
62263
+ const before2 = lstatSync2(lockPath);
62261
62264
  const owner = readdirSync3(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
62262
- const after2 = lstatSync(lockPath);
62265
+ const after2 = lstatSync2(lockPath);
62263
62266
  if (sameCacheLockStatGeneration(before2, after2)) return { stats: after2, owner };
62264
62267
  }
62265
62268
  throw new UrlDownloadError("filesystem", true, "Cache lock changed repeatedly during inspection");
@@ -62770,7 +62773,7 @@ function syncAndPublishPartial(partialPath, localPath) {
62770
62773
  if (code !== "EEXIST") throw error;
62771
62774
  let winner;
62772
62775
  try {
62773
- winner = lstatSync(localPath);
62776
+ winner = lstatSync2(localPath);
62774
62777
  } catch (inspectionError) {
62775
62778
  if (inspectionError.code !== "ENOENT") throw inspectionError;
62776
62779
  throw new UrlDownloadError(
@@ -62966,7 +62969,7 @@ async function reuseOrInvalidateCachedFile(url, localPath, timeoutMs, signal, op
62966
62969
  for (let pass = 0; pass < 3; pass += 1) {
62967
62970
  let before2;
62968
62971
  try {
62969
- before2 = lstatSync(localPath);
62972
+ before2 = lstatSync2(localPath);
62970
62973
  } catch (error) {
62971
62974
  if (error.code === "ENOENT") continue;
62972
62975
  throw error;
@@ -62984,7 +62987,7 @@ async function reuseOrInvalidateCachedFile(url, localPath, timeoutMs, signal, op
62984
62987
  }
62985
62988
  let after2;
62986
62989
  try {
62987
- after2 = lstatSync(localPath);
62990
+ after2 = lstatSync2(localPath);
62988
62991
  } catch (error) {
62989
62992
  if (error.code === "ENOENT") continue;
62990
62993
  throw error;
@@ -63440,7 +63443,7 @@ function scopeSelector(selector, scope, compositionId, authoredRootId, compoundA
63440
63443
  if (/^(html|body|:root)$/i.test(trimmed)) {
63441
63444
  return scopeRootSelectors ? compositionBoxSelector(scope) : selector;
63442
63445
  }
63443
- const compositionIdPattern = new RegExp(`\\[\\s*data-composition-id\\s*=\\s*(["'])${escapeRegExp(compositionId)}\\1\\s*\\]`, "g");
63446
+ const compositionIdPattern = new RegExp(`\\[\\s*data-composition-id\\s*[\\^\\*\\$]?=\\s*(["'])${escapeRegExp(compositionId)}\\1\\s*\\]`, "g");
63444
63447
  if (compositionIdPattern.test(trimmed)) {
63445
63448
  const isRootBoxSelector = trimmed.replace(compositionIdPattern, "").trim() === "";
63446
63449
  if (isRootBoxSelector) {
@@ -63492,7 +63495,12 @@ function scopeCssToComposition(css, compositionId, scopeSelectorOverride, author
63492
63495
  if (!css || !trimmedCompositionId)
63493
63496
  return css;
63494
63497
  const scope = scopeSelectorOverride || `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`;
63495
- const root = postcss.parse(css);
63498
+ let root;
63499
+ try {
63500
+ root = postcss.parse(css);
63501
+ } catch {
63502
+ return "";
63503
+ }
63496
63504
  root.walkRules((rule) => {
63497
63505
  if (isInsideGlobalAtRule(rule))
63498
63506
  return;
@@ -69887,6 +69895,59 @@ function hasAttrName(tagSource, attr) {
69887
69895
  const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
69888
69896
  return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
69889
69897
  }
69898
+ function srcKind(src) {
69899
+ const stripped = src.trim();
69900
+ if (!stripped) return null;
69901
+ const lower = stripped.toLowerCase();
69902
+ if (lower.startsWith("data:")) {
69903
+ const mime = /^data:([^;,]+)/i.exec(stripped)?.[1]?.toLowerCase();
69904
+ if (!mime) return null;
69905
+ if (mime.startsWith("image/")) return "image";
69906
+ if (mime.startsWith("video/")) return "video";
69907
+ return null;
69908
+ }
69909
+ if (lower.startsWith("blob:")) return null;
69910
+ let pathname = stripped;
69911
+ try {
69912
+ if (/^https?:/i.test(stripped)) {
69913
+ pathname = decodeURIComponent(new URL(stripped).pathname);
69914
+ } else {
69915
+ pathname = stripped.split("?")[0]?.split("#")[0] ?? stripped;
69916
+ }
69917
+ } catch {
69918
+ pathname = stripped.split("?")[0]?.split("#")[0] ?? stripped;
69919
+ }
69920
+ const base2 = pathname.split("/").pop() ?? "";
69921
+ const dot = base2.lastIndexOf(".");
69922
+ if (dot < 0) return null;
69923
+ const ext = base2.slice(dot + 1).toLowerCase();
69924
+ if (IMAGE_SRC_EXT.has(ext)) return "image";
69925
+ if (VIDEO_SRC_EXT.has(ext)) return "video";
69926
+ return null;
69927
+ }
69928
+ function findMediaSrcKindMismatchFindings(ctx) {
69929
+ const findings = [];
69930
+ for (const tag of ctx.tags) {
69931
+ if (tag.name !== "video" && tag.name !== "img") continue;
69932
+ const src = readAttr(tag.raw, "src");
69933
+ if (!src) continue;
69934
+ const kind = srcKind(src);
69935
+ if (kind === null) continue;
69936
+ if (tag.name === "video" && kind !== "image") continue;
69937
+ if (tag.name === "img" && kind !== "video") continue;
69938
+ const elementId = readAttr(tag.raw, "id") || void 0;
69939
+ const expected = tag.name === "video" ? "video" : "image";
69940
+ findings.push({
69941
+ code: "media_src_kind_mismatch",
69942
+ severity: "error",
69943
+ message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> src is a ${kind}, not a ${expected}. The producer fail-closes when the tag and file kind disagree.`,
69944
+ elementId,
69945
+ fixHint: tag.name === "video" ? "Use <img> for a still, or point <video> at a video URL (mp4/webm/mov/\u2026)." : "Use <video> for a video URL, or point <img> at a still (png/jpg/webp/\u2026).",
69946
+ snippet: truncateSnippet(tag.raw)
69947
+ });
69948
+ }
69949
+ return findings;
69950
+ }
69890
69951
  function mediaHasResolvableSrc(tag, tags) {
69891
69952
  if (readAttr(tag.raw, "src")) return true;
69892
69953
  const end = tag.closeIndex ?? tag.endIndex;
@@ -71271,7 +71332,7 @@ async function lintHyperframeHtml(html, options = {}) {
71271
71332
  timings: { totalMs: performance.now() - startedAt, ...timings }
71272
71333
  };
71273
71334
  }
71274
- var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, mediaRules, AUDIO_GROUP_TIMING_ATTRS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, compositionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
71335
+ var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, IMAGE_SRC_EXT, VIDEO_SRC_EXT, mediaRules, AUDIO_GROUP_TIMING_ATTRS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, compositionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
71275
71336
  var init_dist3 = __esm({
71276
71337
  "../lint/dist/index.js"() {
71277
71338
  "use strict";
@@ -71398,7 +71459,12 @@ var init_dist3 = __esm({
71398
71459
  let root;
71399
71460
  try {
71400
71461
  root = postcss2.parse(style.content);
71401
- } catch {
71462
+ } catch (error) {
71463
+ findings.push({
71464
+ code: "css_parse_error",
71465
+ severity: "error",
71466
+ message: `CSS parse error: ${error instanceof Error ? error.message : "unknown"}`
71467
+ });
71402
71468
  continue;
71403
71469
  }
71404
71470
  root.walkRules((rule) => {
@@ -71539,7 +71605,7 @@ var init_dist3 = __esm({
71539
71605
  {
71540
71606
  pattern: /crypto\.getRandomValues\s*\(/,
71541
71607
  label: "crypto.getRandomValues()",
71542
- hint: "Remove time-dependent code. Use a seeded PRNG for deterministic renders."
71608
+ hint: "Use a seeded PRNG (e.g. a simple mulberry32) so renders are deterministic across frames."
71543
71609
  },
71544
71610
  {
71545
71611
  pattern: /gsap\.utils\.random\s*\(/,
@@ -71575,6 +71641,31 @@ var init_dist3 = __esm({
71575
71641
  return findings;
71576
71642
  }
71577
71643
  ];
71644
+ IMAGE_SRC_EXT = /* @__PURE__ */ new Set([
71645
+ "jpg",
71646
+ "jpeg",
71647
+ "png",
71648
+ "gif",
71649
+ "bmp",
71650
+ "webp",
71651
+ "svg",
71652
+ "heic",
71653
+ "heif",
71654
+ "tiff",
71655
+ "ico"
71656
+ ]);
71657
+ VIDEO_SRC_EXT = /* @__PURE__ */ new Set([
71658
+ "mp4",
71659
+ "mov",
71660
+ "avi",
71661
+ "webm",
71662
+ "mkv",
71663
+ "flv",
71664
+ "wmv",
71665
+ "m4v",
71666
+ "mpg",
71667
+ "mpeg"
71668
+ ]);
71578
71669
  mediaRules = [
71579
71670
  // duplicate_media_id + duplicate_media_discovery_risk
71580
71671
  ({ tags }) => {
@@ -71778,6 +71869,8 @@ var init_dist3 = __esm({
71778
71869
  }
71779
71870
  return findings;
71780
71871
  },
71872
+ // media_src_kind_mismatch
71873
+ findMediaSrcKindMismatchFindings,
71781
71874
  // placeholder_media_url
71782
71875
  ({ tags }) => {
71783
71876
  const findings = [];
@@ -74725,7 +74818,7 @@ var init_extractedFrameIndex = __esm({
74725
74818
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
74726
74819
  import {
74727
74820
  existsSync as existsSync9,
74728
- lstatSync as lstatSync2,
74821
+ lstatSync as lstatSync3,
74729
74822
  mkdirSync as mkdirSync5,
74730
74823
  readdirSync as readdirSync5,
74731
74824
  renameSync,
@@ -74829,7 +74922,7 @@ function isPartialChild(name) {
74829
74922
  }
74830
74923
  function directorySizeBytes(path) {
74831
74924
  try {
74832
- const stat = lstatSync2(path);
74925
+ const stat = lstatSync3(path);
74833
74926
  if (!stat.isDirectory()) return stat.size;
74834
74927
  } catch {
74835
74928
  return 0;
@@ -74844,7 +74937,7 @@ function directorySizeBytes(path) {
74844
74937
  for (const child of children) {
74845
74938
  const childPath = join10(path, child);
74846
74939
  try {
74847
- const stat = lstatSync2(childPath);
74940
+ const stat = lstatSync3(childPath);
74848
74941
  if (stat.isDirectory()) {
74849
74942
  total += directorySizeBytes(childPath);
74850
74943
  } else {
@@ -79117,12 +79210,17 @@ var init_fileServer = __esm({
79117
79210
  ".gif": "image/gif",
79118
79211
  ".svg": "image/svg+xml",
79119
79212
  ".webp": "image/webp",
79213
+ ".avif": "image/avif",
79214
+ ".ico": "image/vnd.microsoft.icon",
79120
79215
  ".mp4": "video/mp4",
79216
+ ".mov": "video/quicktime",
79121
79217
  ".webm": "video/webm",
79122
79218
  ".mp3": "audio/mpeg",
79123
79219
  ".wav": "audio/wav",
79124
79220
  ".ogg": "audio/ogg",
79125
79221
  ".aac": "audio/aac",
79222
+ ".flac": "audio/flac",
79223
+ ".m4a": "audio/mp4",
79126
79224
  ".woff": "font/woff",
79127
79225
  ".woff2": "font/woff2",
79128
79226
  ".ttf": "font/ttf",
@@ -84141,7 +84239,15 @@ __export(fontCompression_exports, {
84141
84239
  fontToDataUri: () => fontToDataUri
84142
84240
  });
84143
84241
  import { createHash as createHash6 } from "crypto";
84144
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
84242
+ import {
84243
+ existsSync as existsSync21,
84244
+ mkdirSync as mkdirSync10,
84245
+ mkdtempSync as mkdtempSync5,
84246
+ readFileSync as readFileSync7,
84247
+ renameSync as renameSync4,
84248
+ rmSync as rmSync8,
84249
+ writeFileSync as writeFileSync8
84250
+ } from "fs";
84145
84251
  import { homedir as homedir4, tmpdir as tmpdir4 } from "os";
84146
84252
  import { dirname as dirname11, join as join24 } from "path";
84147
84253
  async function compressToWoff2(input) {
@@ -84168,15 +84274,17 @@ function readCachedCompression(path) {
84168
84274
  }
84169
84275
  }
84170
84276
  function cacheCompression(path, compressed) {
84171
- const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
84277
+ let tempDir;
84172
84278
  try {
84173
84279
  mkdirSync10(dirname11(path), { recursive: true });
84280
+ tempDir = mkdtempSync5(join24(dirname11(path), ".compression-"));
84281
+ const tmpPath = join24(tempDir, "font.woff2");
84174
84282
  writeFileSync8(tmpPath, compressed, { flag: "wx", mode: 420 });
84175
84283
  renameSync4(tmpPath, path);
84176
84284
  } catch {
84177
84285
  } finally {
84178
84286
  try {
84179
- rmSync8(tmpPath, { force: true });
84287
+ if (tempDir) rmSync8(tempDir, { recursive: true, force: true });
84180
84288
  } catch {
84181
84289
  }
84182
84290
  }
@@ -84226,7 +84334,7 @@ init_src();
84226
84334
  import {
84227
84335
  existsSync as existsSync30,
84228
84336
  mkdirSync as mkdirSync17,
84229
- mkdtempSync as mkdtempSync7,
84337
+ mkdtempSync as mkdtempSync9,
84230
84338
  readFileSync as readFileSync12,
84231
84339
  readdirSync as readdirSync11,
84232
84340
  rmSync as rmSync13,
@@ -84236,7 +84344,7 @@ import {
84236
84344
  appendFileSync
84237
84345
  } from "fs";
84238
84346
  import { tmpdir as tmpdir7 } from "os";
84239
- import { join as join41, dirname as dirname16, resolve as resolve13 } from "path";
84347
+ import { join as join42, dirname as dirname17, resolve as resolve13 } from "path";
84240
84348
  import { totalmem as totalmem3 } from "os";
84241
84349
  import { randomUUID as randomUUID3 } from "crypto";
84242
84350
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -84390,12 +84498,17 @@ var MIME_TYPES2 = {
84390
84498
  ".gif": "image/gif",
84391
84499
  ".svg": "image/svg+xml",
84392
84500
  ".webp": "image/webp",
84501
+ ".avif": "image/avif",
84502
+ ".ico": "image/vnd.microsoft.icon",
84393
84503
  ".mp4": "video/mp4",
84504
+ ".mov": "video/quicktime",
84394
84505
  ".webm": "video/webm",
84395
84506
  ".mp3": "audio/mpeg",
84396
84507
  ".wav": "audio/wav",
84397
84508
  ".ogg": "audio/ogg",
84398
84509
  ".aac": "audio/aac",
84510
+ ".flac": "audio/flac",
84511
+ ".m4a": "audio/mp4",
84399
84512
  ".woff": "font/woff",
84400
84513
  ".woff2": "font/woff2",
84401
84514
  ".ttf": "font/ttf",
@@ -86673,7 +86786,9 @@ function isToleratedShortClipBoundaryMiss(report, threshold) {
86673
86786
  }
86674
86787
  function expectedFramesForVideo(video, entry, fps) {
86675
86788
  const rounding = entry && !entry.metadata.isVFR ? "nearest" : "ceil";
86676
- const slotFrames = expectedFramesForClip(video.start, video.end, fps, rounding);
86789
+ const playbackRate = normalizePlaybackRate(video.playbackRate ?? 1);
86790
+ const slotSourceDuration = Math.max(0, video.end - video.start) * playbackRate;
86791
+ const slotFrames = expectedFramesForClip(0, slotSourceDuration, fps, rounding);
86677
86792
  if (!entry) return slotFrames;
86678
86793
  const sourceDuration = resolvePlayableVideoDuration(entry.metadata) - video.mediaStart;
86679
86794
  if (!Number.isFinite(sourceDuration) || sourceDuration <= 0) return slotFrames;
@@ -86733,7 +86848,7 @@ init_compiler();
86733
86848
  init_subCompositionValidity();
86734
86849
  init_assetResolution();
86735
86850
  import { createReadStream as createReadStream4, existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync10 } from "fs";
86736
- import { join as join27, dirname as dirname13, resolve as resolve12, basename as basename6 } from "path";
86851
+ import { join as join27, dirname as dirname13, resolve as resolve12, basename as basename6, relative as relative2 } from "path";
86737
86852
 
86738
86853
  // ../producer/src/services/renderMediaCollector.ts
86739
86854
  init_esm10();
@@ -86819,7 +86934,7 @@ init_src();
86819
86934
 
86820
86935
  // ../producer/src/services/deterministicFonts.ts
86821
86936
  import { createHash as createHash7 } from "crypto";
86822
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
86937
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
86823
86938
  import { homedir as homedir5, tmpdir as tmpdir5 } from "os";
86824
86939
  import { join as join25 } from "path";
86825
86940
 
@@ -86828,7 +86943,7 @@ init_dist();
86828
86943
 
86829
86944
  // ../core/dist/fonts/systemFontLocator.js
86830
86945
  import { execFileSync as execFileSync2 } from "child_process";
86831
- import { existsSync as existsSync20, lstatSync as lstatSync3, readdirSync as readdirSync9, realpathSync as realpathSync3 } from "fs";
86946
+ import { existsSync as existsSync20, lstatSync as lstatSync4, readdirSync as readdirSync9, realpathSync as realpathSync3 } from "fs";
86832
86947
  import { homedir as homedir3, platform as platform2 } from "os";
86833
86948
  import { join as join23, resolve as resolve10 } from "path";
86834
86949
  var SYSTEM_FONT_SIZE_LIMIT = 5 * 1024 * 1024;
@@ -86882,9 +86997,9 @@ function isPathBounded(filePath) {
86882
86997
  }
86883
86998
  function isRegularFile(filePath) {
86884
86999
  try {
86885
- const lst = lstatSync3(filePath);
87000
+ const lst = lstatSync4(filePath);
86886
87001
  if (lst.isSymbolicLink())
86887
- return isPathBounded(filePath) && lstatSync3(realpathSync3(filePath)).isFile();
87002
+ return isPathBounded(filePath) && lstatSync4(realpathSync3(filePath)).isFile();
86888
87003
  return lst.isFile();
86889
87004
  } catch {
86890
87005
  return false;
@@ -87431,6 +87546,11 @@ function extractRequestedFontFamilies(html) {
87431
87546
  function fontFormatHint(src) {
87432
87547
  return src.startsWith("data:font/collection;") ? "collection" : "woff2";
87433
87548
  }
87549
+ var BUNDLED_SUBSET_UNICODE_RANGE = "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD";
87550
+ function isBundledSubsetRange(unicodeRange) {
87551
+ const normalize = (range) => range.toLowerCase().replace(/\s+/g, "");
87552
+ return unicodeRange !== void 0 && normalize(unicodeRange) === normalize(BUNDLED_SUBSET_UNICODE_RANGE);
87553
+ }
87434
87554
  function buildFontFaceRule(familyName, src, weight, style, unicodeRange) {
87435
87555
  return [
87436
87556
  "@font-face {",
@@ -87502,16 +87622,25 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
87502
87622
  const canonical = CANONICAL_FONTS[canonicalKey];
87503
87623
  if (!canonical) continue;
87504
87624
  const coveredWeights = /* @__PURE__ */ new Set();
87625
+ const bundledRules = [];
87505
87626
  for (const face of canonical.faces) {
87506
87627
  const style = face.style || "normal";
87507
87628
  const src = fontDataUri(canonical.packageName, face.weight, style);
87508
- rules.push(buildFontFaceRule(originalCaseFamily, src, face.weight, style));
87629
+ bundledRules.push(
87630
+ buildFontFaceRule(
87631
+ originalCaseFamily,
87632
+ src,
87633
+ face.weight,
87634
+ style,
87635
+ BUNDLED_SUBSET_UNICODE_RANGE
87636
+ )
87637
+ );
87509
87638
  coveredWeights.add(coverageKey(face.weight, style));
87510
87639
  }
87511
87640
  const canonicalFamily = resolveAliasDisplayName(normalizedFamily);
87512
87641
  const googleFaces2 = canonicalFamily ? await fetchGoogleFont(canonicalFamily, options, fontText) : [];
87513
87642
  const supplementary = googleFaces2.filter(
87514
- (face) => !coveredWeights.has(coverageKey(face.weight, face.style))
87643
+ (face) => !coveredWeights.has(coverageKey(face.weight, face.style)) || !isBundledSubsetRange(face.unicodeRange)
87515
87644
  );
87516
87645
  const runs = groupFacesBySource(supplementary).flatMap(
87517
87646
  (group) => partitionWeightRuns(group, coveredWeights)
@@ -87532,6 +87661,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
87532
87661
  )
87533
87662
  );
87534
87663
  }
87664
+ rules.push(...bundledRules);
87535
87665
  continue;
87536
87666
  }
87537
87667
  const googleFaces = await fetchGoogleFont(originalCaseFamily, options, fontText);
@@ -87600,7 +87730,7 @@ function resolveFontCacheRoot() {
87600
87730
  return process.env.HYPERFRAMES_FONT_CACHE_DIR;
87601
87731
  }
87602
87732
  if (process.env.AWS_LAMBDA_FUNCTION_NAME) {
87603
- lambdaFontCacheRoot ??= mkdtempSync5(join25(tmpdir5(), "hyperframes-fonts-"));
87733
+ lambdaFontCacheRoot ??= mkdtempSync6(join25(tmpdir5(), "hyperframes-fonts-"));
87604
87734
  return lambdaFontCacheRoot;
87605
87735
  }
87606
87736
  return join25(homedir5(), ".cache", "hyperframes", "fonts");
@@ -87609,10 +87739,24 @@ var WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWeb
87609
87739
  function fontSlug(familyName) {
87610
87740
  return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
87611
87741
  }
87742
+ var ephemeralFontCacheRoot;
87612
87743
  function fontCacheDir(slug) {
87613
87744
  const dir = join25(resolveFontCacheRoot(), slug);
87614
87745
  if (!existsSync22(dir)) {
87615
- mkdirSync11(dir, { recursive: true });
87746
+ try {
87747
+ mkdirSync11(dir, { recursive: true });
87748
+ } catch {
87749
+ const firstFallback = ephemeralFontCacheRoot === void 0;
87750
+ ephemeralFontCacheRoot ??= mkdtempSync6(join25(tmpdir5(), "hyperframes-fonts-"));
87751
+ const fallback = join25(ephemeralFontCacheRoot, slug);
87752
+ mkdirSync11(fallback, { recursive: true });
87753
+ if (firstFallback) {
87754
+ defaultLogger.warn(
87755
+ `Font cache directory is unwritable (${dir}). Using temporary fallback \u2014 fonts will re-download each run. Fix with: chmod 755 ${resolveFontCacheRoot()}`
87756
+ );
87757
+ }
87758
+ return fallback;
87759
+ }
87616
87760
  }
87617
87761
  return dir;
87618
87762
  }
@@ -87829,17 +87973,108 @@ async function fetchGoogleFont(familyName, options, fontText) {
87829
87973
  return faces;
87830
87974
  }
87831
87975
  var GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH = 1700;
87976
+ var SMALL_TO_FULL_KANA = /* @__PURE__ */ new Map([
87977
+ ["\u3041", "\u3042"],
87978
+ ["\u3043", "\u3044"],
87979
+ ["\u3045", "\u3046"],
87980
+ ["\u3047", "\u3048"],
87981
+ ["\u3049", "\u304A"],
87982
+ ["\u3063", "\u3064"],
87983
+ ["\u3083", "\u3084"],
87984
+ ["\u3085", "\u3086"],
87985
+ ["\u3087", "\u3088"],
87986
+ ["\u308E", "\u308F"],
87987
+ ["\u30A1", "\u30A2"],
87988
+ ["\u30A3", "\u30A4"],
87989
+ ["\u30A5", "\u30A6"],
87990
+ ["\u30A7", "\u30A8"],
87991
+ ["\u30A9", "\u30AA"],
87992
+ ["\u30C3", "\u30C4"],
87993
+ ["\u30E3", "\u30E4"],
87994
+ ["\u30E5", "\u30E6"],
87995
+ ["\u30E7", "\u30E8"],
87996
+ ["\u30EE", "\u30EF"],
87997
+ ["\u30F5", "\u30AB"],
87998
+ ["\u30F6", "\u30B1"]
87999
+ ]);
88000
+ function collectLangAttributes(document2) {
88001
+ const locales = /* @__PURE__ */ new Set();
88002
+ for (const element of document2.querySelectorAll("[lang]")) {
88003
+ const lang = element.getAttribute("lang");
88004
+ if (!lang) continue;
88005
+ const primary = lang.split("-")[0].toLowerCase();
88006
+ try {
88007
+ Intl.getCanonicalLocales(primary);
88008
+ locales.add(primary);
88009
+ } catch {
88010
+ }
88011
+ }
88012
+ return locales;
88013
+ }
88014
+ function addCaseClosure(out, character, locales) {
88015
+ out.add(character);
88016
+ for (const variant of `${character.toUpperCase()}${character.toLowerCase()}`) {
88017
+ out.add(variant);
88018
+ }
88019
+ for (const locale of locales) {
88020
+ for (const variant of `${character.toLocaleUpperCase(locale)}${character.toLocaleLowerCase(locale)}`) {
88021
+ out.add(variant);
88022
+ }
88023
+ }
88024
+ }
88025
+ function addFullwidthVariants(chars) {
88026
+ for (const character of [...chars]) {
88027
+ const code = character.codePointAt(0) ?? 0;
88028
+ if (code >= 33 && code <= 126) {
88029
+ chars.add(String.fromCodePoint(code + 65248));
88030
+ }
88031
+ }
88032
+ }
88033
+ function addFullSizeKanaVariants(chars) {
88034
+ for (const character of [...chars]) {
88035
+ const full = SMALL_TO_FULL_KANA.get(character);
88036
+ if (full) chars.add(full);
88037
+ }
88038
+ }
88039
+ var FULL_WIDTH_KEYWORD_RE = /\bfull-width\b/;
88040
+ var FULL_SIZE_KANA_KEYWORD_RE = /\bfull-size-kana\b/;
88041
+ var DECLARATION_BOUNDARY_RE = /[;{}]/;
88042
+ function skipWhitespace(s, pos) {
88043
+ while (pos < s.length && " \n\r\f\v".includes(s[pos])) pos += 1;
88044
+ return pos;
88045
+ }
88046
+ function findDeclarationEnd(s, pos) {
88047
+ const match = DECLARATION_BOUNDARY_RE.exec(s.slice(pos));
88048
+ return match ? pos + match.index : s.length;
88049
+ }
88050
+ function hasTextTransformKeyword(html, keyword) {
88051
+ const haystack = html.toLowerCase();
88052
+ const property = "text-transform";
88053
+ let from = 0;
88054
+ for (; ; ) {
88055
+ const at2 = haystack.indexOf(property, from);
88056
+ if (at2 === -1) return false;
88057
+ const afterProp = skipWhitespace(haystack, at2 + property.length);
88058
+ if (haystack[afterProp] !== ":") {
88059
+ from = at2 + property.length;
88060
+ continue;
88061
+ }
88062
+ const end = findDeclarationEnd(haystack, afterProp + 1);
88063
+ if (keyword.test(haystack.slice(afterProp + 1, end))) return true;
88064
+ from = end;
88065
+ }
88066
+ }
87832
88067
  function extractGoogleFontsText(html) {
87833
88068
  const { document: document2 } = parseHTML(html);
87834
88069
  const decodedBodyText = document2.body?.textContent ?? "";
87835
- const characters = [...Array.from(html), ...Array.from(decodedBodyText)];
88070
+ const locales = collectLangAttributes(document2);
87836
88071
  const uniqueCharacters = /* @__PURE__ */ new Set();
87837
- for (const character of characters) {
87838
- uniqueCharacters.add(character);
87839
- for (const variant of `${character.toUpperCase()}${character.toLowerCase()}`) {
87840
- uniqueCharacters.add(variant);
87841
- }
88072
+ for (const character of /* @__PURE__ */ new Set([...Array.from(html), ...Array.from(decodedBodyText)])) {
88073
+ addCaseClosure(uniqueCharacters, character, locales);
87842
88074
  }
88075
+ if (hasTextTransformKeyword(html, FULL_WIDTH_KEYWORD_RE)) addFullwidthVariants(uniqueCharacters);
88076
+ if (hasTextTransformKeyword(html, FULL_SIZE_KANA_KEYWORD_RE))
88077
+ addFullSizeKanaVariants(uniqueCharacters);
87843
88078
  const fontText = [...uniqueCharacters].join("");
87844
88079
  return encodeURIComponent(fontText).length <= GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH ? fontText : void 0;
87845
88080
  }
@@ -88688,6 +88923,7 @@ async function preflightCompositionAssetMediaTypes(input) {
88688
88923
  function logRemoteDownloadTelemetry(event) {
88689
88924
  defaultLogger.info("[Compiler] Remote asset download integrity", { ...event });
88690
88925
  }
88926
+ var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
88691
88927
  function parseSubCompHtmlForValidity(html) {
88692
88928
  return parseHTML(html).document;
88693
88929
  }
@@ -88922,6 +89158,18 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
88922
89158
  return { duration, resolvedPath: filePath };
88923
89159
  });
88924
89160
  }
89161
+ function markInferredVariableMediaDurations(html, unresolvedMedia) {
89162
+ const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
89163
+ if (unresolvedIds.size === 0) return html;
89164
+ const { document: document2 } = parseHTML(html);
89165
+ let changed = false;
89166
+ for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
89167
+ if (!unresolvedIds.has(element.id)) continue;
89168
+ element.setAttribute(INFERRED_MEDIA_DURATION_ATTR, "");
89169
+ changed = true;
89170
+ }
89171
+ return changed ? document2.toString() : html;
89172
+ }
88925
89173
  async function compileHtmlFile(html, baseDir, downloadDir, log) {
88926
89174
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
88927
89175
  const mediaUnresolved = unresolved.filter(
@@ -88945,7 +89193,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
88945
89193
  const resolutions = resolvedResults.filter(
88946
89194
  (r) => r.duration != null && Number.isFinite(r.duration)
88947
89195
  );
88948
- let compiledHtml = resolutions.length > 0 ? injectDurations(staticCompiled, resolutions) : staticCompiled;
89196
+ const markedStaticHtml = markInferredVariableMediaDurations(staticCompiled, mediaUnresolved);
89197
+ let compiledHtml = resolutions.length > 0 ? injectDurations(markedStaticHtml, resolutions) : markedStaticHtml;
88949
89198
  const preResolved = extractResolvedMedia(compiledHtml);
88950
89199
  const clampResults = await Promise.all(
88951
89200
  preResolved.filter((el) => !!el.src && !el.loop).map(async (el) => {
@@ -89715,8 +89964,38 @@ function rewriteUnresolvableGsapToCdn(html, projectDir) {
89715
89964
  }
89716
89965
  );
89717
89966
  }
89967
+ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
89968
+ if (!isPathInside3(htmlPath, projectDir)) return html;
89969
+ const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
89970
+ if (!entryPath.includes("/")) return html;
89971
+ const { document: document2 } = parseHTML(html);
89972
+ const assetExists = (path) => existsSync25(resolve12(projectDir, path));
89973
+ rewriteAssetPaths(
89974
+ document2.querySelectorAll("[src], [href]"),
89975
+ entryPath,
89976
+ (el, attr) => el.getAttribute(attr),
89977
+ (el, attr, value) => el.setAttribute(attr, value),
89978
+ assetExists
89979
+ );
89980
+ rewriteInlineStyleAssetUrls(
89981
+ document2.querySelectorAll("[style]"),
89982
+ entryPath,
89983
+ (el) => el.getAttribute("style"),
89984
+ (el, value) => el.setAttribute("style", value),
89985
+ assetExists
89986
+ );
89987
+ for (const style of document2.querySelectorAll("style")) {
89988
+ style.textContent = rewriteCssAssetUrls(style.textContent || "", entryPath, assetExists);
89989
+ }
89990
+ return document2.toString();
89991
+ }
89718
89992
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
89719
- const rawHtml = rewriteUnresolvableGsapToCdn(readFileSync10(htmlPath, "utf-8"), projectDir);
89993
+ const entryHtml = rebaseDirectEntryAssetPaths(
89994
+ readFileSync10(htmlPath, "utf-8"),
89995
+ projectDir,
89996
+ htmlPath
89997
+ );
89998
+ const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
89720
89999
  assertSubCompositionsUsable(rawHtml, projectDir);
89721
90000
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
89722
90001
  rawHtml,
@@ -89873,6 +90152,8 @@ async function discoverMediaFromBrowser(page) {
89873
90152
  const start = parseFloat(htmlEl.getAttribute("data-start") || "0");
89874
90153
  const endRaw = htmlEl.getAttribute("data-end");
89875
90154
  const durationRaw = htmlEl.getAttribute("data-duration");
90155
+ const durationInferred = htmlEl.hasAttribute("data-hf-inferred-duration");
90156
+ const intrinsicDuration = isImage ? 0 : htmlEl.duration;
89876
90157
  const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
89877
90158
  const mediaStartRaw = htmlEl.getAttribute("data-media-start");
89878
90159
  const loop = htmlEl.hasAttribute("loop");
@@ -89886,6 +90167,8 @@ async function discoverMediaFromBrowser(page) {
89886
90167
  start,
89887
90168
  endRaw,
89888
90169
  durationRaw,
90170
+ intrinsicDuration,
90171
+ durationInferred,
89889
90172
  playbackStartRaw,
89890
90173
  mediaStartRaw,
89891
90174
  loop,
@@ -89896,18 +90179,20 @@ async function discoverMediaFromBrowser(page) {
89896
90179
  });
89897
90180
  return results;
89898
90181
  });
89899
- return elements.map(({ endRaw, durationRaw, playbackStartRaw, mediaStartRaw, ...element }) => ({
89900
- ...element,
89901
- end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
89902
- duration: parseStrictFiniteTimingNumber(durationRaw) ?? 0,
89903
- mediaStart: readMediaStart({
89904
- getAttribute(name) {
89905
- if (name === "data-playback-start") return playbackStartRaw;
89906
- if (name === "data-media-start") return mediaStartRaw;
89907
- return null;
89908
- }
90182
+ return elements.map(
90183
+ ({ endRaw, durationRaw, intrinsicDuration, playbackStartRaw, mediaStartRaw, ...element }) => ({
90184
+ ...element,
90185
+ end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
90186
+ duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
90187
+ mediaStart: readMediaStart({
90188
+ getAttribute(name) {
90189
+ if (name === "data-playback-start") return playbackStartRaw;
90190
+ if (name === "data-media-start") return mediaStartRaw;
90191
+ return null;
90192
+ }
90193
+ })
89909
90194
  })
89910
- }));
90195
+ );
89911
90196
  }
89912
90197
  async function discoverAudioVolumeAutomationFromTimeline(page, audioIds, compositionDuration, sampleFps) {
89913
90198
  if (audioIds.length === 0 || compositionDuration <= 0) return [];
@@ -90353,6 +90638,11 @@ function hasVariableBoundMedia(html, variables) {
90353
90638
  return Boolean(variableId && Object.hasOwn(variables, variableId));
90354
90639
  });
90355
90640
  }
90641
+ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
90642
+ if (projectedEnd <= 0) return existingEnd;
90643
+ if (sourceChanged && durationInferred) return projectedEnd;
90644
+ return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
90645
+ }
90356
90646
  function hasRuntimeInsertedMedia(html) {
90357
90647
  const { document: document2 } = parseHTML(html);
90358
90648
  const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
@@ -90584,7 +90874,8 @@ async function runProbeStage(input) {
90584
90874
  if (existingVideoIds.has(el.id)) {
90585
90875
  const existing = composition.videos.find((v) => v.id === el.id);
90586
90876
  if (existing) {
90587
- if (existing.src !== src) {
90877
+ const sourceChanged = existing.src !== src;
90878
+ if (sourceChanged) {
90588
90879
  existing.src = src;
90589
90880
  }
90590
90881
  const projectedEnd = projectBrowserEndToCompositionTimeline(
@@ -90592,9 +90883,12 @@ async function runProbeStage(input) {
90592
90883
  el.start,
90593
90884
  resolveBrowserMediaEnd(el.start, el.end, el.duration)
90594
90885
  );
90595
- if (projectedEnd > 0) {
90596
- existing.end = existing.end <= 0 ? projectedEnd : Math.min(existing.end, projectedEnd);
90597
- }
90886
+ existing.end = reconcileBrowserMediaEnd(
90887
+ existing.end,
90888
+ projectedEnd,
90889
+ sourceChanged,
90890
+ el.durationInferred
90891
+ );
90598
90892
  if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
90599
90893
  existing.mediaStart = el.mediaStart;
90600
90894
  }
@@ -90621,7 +90915,8 @@ async function runProbeStage(input) {
90621
90915
  if (existingAudioIds.has(el.id)) {
90622
90916
  const existing = composition.audios.find((a) => a.id === el.id);
90623
90917
  if (existing) {
90624
- if (existing.src !== src) {
90918
+ const sourceChanged = existing.src !== src;
90919
+ if (sourceChanged) {
90625
90920
  existing.src = src;
90626
90921
  }
90627
90922
  const projectedEnd = projectBrowserEndToCompositionTimeline(
@@ -90629,9 +90924,12 @@ async function runProbeStage(input) {
90629
90924
  el.start,
90630
90925
  resolveBrowserMediaEnd(el.start, el.end, el.duration)
90631
90926
  );
90632
- if (projectedEnd > 0) {
90633
- existing.end = existing.end <= 0 ? projectedEnd : Math.min(existing.end, projectedEnd);
90634
- }
90927
+ existing.end = reconcileBrowserMediaEnd(
90928
+ existing.end,
90929
+ projectedEnd,
90930
+ sourceChanged,
90931
+ el.durationInferred
90932
+ );
90635
90933
  if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
90636
90934
  existing.mediaStart = el.mediaStart;
90637
90935
  }
@@ -91312,9 +91610,32 @@ async function runAudioStage(input) {
91312
91610
 
91313
91611
  // ../producer/src/services/render/stages/captureStage.ts
91314
91612
  init_src();
91613
+ import { statfsSync } from "fs";
91315
91614
  function shouldAllowAdaptiveCaptureRetry(workerCount, _explicitlyConfigured) {
91316
91615
  return workerCount > 1;
91317
91616
  }
91617
+ function estimateDiskCaptureBytes(totalFrames, captureOptions) {
91618
+ const scale = captureOptions.deviceScaleFactor ?? 1;
91619
+ const outputWidth = Math.ceil(captureOptions.width * scale);
91620
+ const outputHeight = Math.ceil(captureOptions.height * scale);
91621
+ return Math.ceil(totalFrames) * outputWidth * outputHeight * 4;
91622
+ }
91623
+ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeDiskBytes = (path) => {
91624
+ try {
91625
+ const stat = statfsSync(path);
91626
+ return stat.bavail * stat.bsize;
91627
+ } catch {
91628
+ return null;
91629
+ }
91630
+ }) {
91631
+ const freeBytes = freeDiskBytes(framesDir);
91632
+ if (freeBytes === null) return;
91633
+ const estimatedBytes = estimateDiskCaptureBytes(totalFrames, captureOptions);
91634
+ if (estimatedBytes <= freeBytes * 0.9) return;
91635
+ throw new Error(
91636
+ `Disk capture may need ~${(estimatedBytes / 1e6).toFixed(1)} MB of temporary frame storage, but only ${(freeBytes / 1e6).toFixed(1)} MB is free at ${framesDir}. Re-run with --low-memory-mode to stream frames, raise PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS if streaming is supported, or free up disk space.`
91637
+ );
91638
+ }
91318
91639
  async function runCaptureStage(input) {
91319
91640
  const {
91320
91641
  fileServer,
@@ -91353,6 +91674,8 @@ async function runCaptureStage(input) {
91353
91674
  );
91354
91675
  }
91355
91676
  }
91677
+ const captureOptions = buildCaptureOptions();
91678
+ assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions);
91356
91679
  if (workerCount > 1) {
91357
91680
  const attempts = await executeDiskCaptureWithAdaptiveRetry({
91358
91681
  serverUrl: fileServer.url,
@@ -91362,7 +91685,7 @@ async function runCaptureStage(input) {
91362
91685
  initialWorkerCount: workerCount,
91363
91686
  allowRetry: shouldAllowAdaptiveCaptureRetry(workerCount, job.config.workers !== void 0),
91364
91687
  frameExt: needsAlpha ? "png" : "jpg",
91365
- captureOptions: buildCaptureOptions(),
91688
+ captureOptions,
91366
91689
  createBeforeCaptureHook: createRenderVideoFrameInjector,
91367
91690
  abortSignal,
91368
91691
  frameRangeStart: frameRange?.startFrame,
@@ -91400,7 +91723,7 @@ async function runCaptureStage(input) {
91400
91723
  const session = probeSession ?? await createCaptureSession(
91401
91724
  fileServer.url,
91402
91725
  framesDir,
91403
- buildCaptureOptions(),
91726
+ captureOptions,
91404
91727
  videoInjector,
91405
91728
  captureCfg
91406
91729
  );
@@ -91940,11 +92263,11 @@ import {
91940
92263
  constants as constants2,
91941
92264
  fstatSync as fstatSync2,
91942
92265
  mkdirSync as mkdirSync14,
91943
- mkdtempSync as mkdtempSync6,
92266
+ mkdtempSync as mkdtempSync7,
91944
92267
  openSync as openSync4,
91945
92268
  readFileSync as readFileSync11,
91946
92269
  rmSync as rmSync10,
91947
- statfsSync
92270
+ statfsSync as statfsSync2
91948
92271
  } from "fs";
91949
92272
  import { join as join33 } from "path";
91950
92273
  var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
@@ -92123,7 +92446,7 @@ function assertHdrExtractionDiskHeadroom(framesDir, plannedVideos, fps, log) {
92123
92446
  }
92124
92447
  let freeBytes;
92125
92448
  try {
92126
- const stat = statfsSync(framesDir);
92449
+ const stat = statfsSync2(framesDir);
92127
92450
  freeBytes = stat.bavail * stat.bsize;
92128
92451
  } catch {
92129
92452
  return { estimatedBytes, budgetBytes: configuredBudget };
@@ -92190,7 +92513,7 @@ async function extractHdrVideoFrames(args) {
92190
92513
  const window3 = extractionWindows.get(videoId);
92191
92514
  if (!video || !window3) continue;
92192
92515
  mkdirSync14(framesDir, { recursive: true });
92193
- const frameDir = mkdtempSync6(join33(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
92516
+ const frameDir = mkdtempSync7(join33(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
92194
92517
  createdFrameDirs.add(frameDir);
92195
92518
  const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
92196
92519
  const rawPath = join33(frameDir, "frames.rgb48le");
@@ -94189,7 +94512,8 @@ async function runEncodeStage(input) {
94189
94512
  return { encodeMs: Date.now() - stage5Start };
94190
94513
  }
94191
94514
  updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
94192
- const scaledEncodeTimeout = Math.ceil((job.duration ?? 0) * 4e3);
94515
+ const baseScaledEncodeTimeout = Math.ceil((job.duration ?? 0) * 4e3);
94516
+ const scaledEncodeTimeout = baseScaledEncodeTimeout * (job.config.quality === "high" ? 6 : 1);
94193
94517
  const videoEngineCfg = scaledEncodeTimeout > engineCfg.ffmpegEncodeTimeout ? { ...engineCfg, ffmpegEncodeTimeout: scaledEncodeTimeout } : engineCfg;
94194
94518
  const frameExt = needsAlpha ? "png" : "jpg";
94195
94519
  const framePattern = `frame_%06d.${frameExt}`;
@@ -94242,8 +94566,30 @@ import { extname as extname6 } from "path";
94242
94566
  init_src();
94243
94567
  init_dist2();
94244
94568
  import { spawn as spawn5 } from "child_process";
94245
- import { rmSync as rmSync12 } from "fs";
94569
+ import { mkdtempSync as mkdtempSync8, renameSync as renameSync6, rmSync as rmSync12 } from "fs";
94570
+ import { dirname as dirname16, join as join41 } from "path";
94246
94571
  var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
94572
+ var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
94573
+ var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
94574
+ function buildAacTruePeakCorrectionArgs(inputPath, outputPath, targetDurationSeconds, attenuationDb) {
94575
+ return [
94576
+ "-i",
94577
+ inputPath,
94578
+ "-map",
94579
+ "0:a:0",
94580
+ "-vn",
94581
+ "-af",
94582
+ `volume=${attenuationDb.toFixed(3)}dB`,
94583
+ "-t",
94584
+ formatSeconds(targetDurationSeconds),
94585
+ "-c:a",
94586
+ "aac",
94587
+ "-b:a",
94588
+ "192k",
94589
+ "-y",
94590
+ outputPath
94591
+ ];
94592
+ }
94247
94593
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
94248
94594
  const delta = targetDurationSeconds - sourceDurationSeconds;
94249
94595
  const targetSec = formatSeconds(targetDurationSeconds);
@@ -94318,6 +94664,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
94318
94664
  const probeVideo = input.probeVideoFrameInfo ?? ((videoPath) => defaultProbeVideoFrameInfo(videoPath, input.signal));
94319
94665
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
94320
94666
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, input.signal));
94667
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : defaultProbeAudioTruePeakDbfs);
94321
94668
  const [videoResult, audioResult] = await Promise.allSettled([
94322
94669
  probeVideo(input.videoPath),
94323
94670
  probeAudio(input.audioPath, input.signal)
@@ -94389,6 +94736,26 @@ async function padOrTrimAudioToVideoFrameCount(input) {
94389
94736
  } finally {
94390
94737
  for (const path of plan2.cleanupPaths) rmSync12(path, { force: true });
94391
94738
  }
94739
+ if (probeTruePeak) {
94740
+ const correction = await enforceAacTruePeak({
94741
+ audioPath: input.outputPath,
94742
+ targetDurationSeconds,
94743
+ signal: input.signal,
94744
+ probeTruePeak,
94745
+ runner
94746
+ });
94747
+ if (!correction.success) {
94748
+ return {
94749
+ success: false,
94750
+ outputPath: input.outputPath,
94751
+ targetDurationSeconds,
94752
+ sourceDurationSeconds: audioInfo.durationSeconds,
94753
+ operation: plan2.operation,
94754
+ error: correction.error,
94755
+ failureReason: correction.failureReason
94756
+ };
94757
+ }
94758
+ }
94392
94759
  return {
94393
94760
  success: true,
94394
94761
  outputPath: input.outputPath,
@@ -94397,6 +94764,60 @@ async function padOrTrimAudioToVideoFrameCount(input) {
94397
94764
  operation: plan2.operation
94398
94765
  };
94399
94766
  }
94767
+ async function enforceAacTruePeak(input) {
94768
+ let scratchDir;
94769
+ try {
94770
+ scratchDir = mkdtempSync8(join41(dirname16(input.audioPath), ".true-peak-"));
94771
+ const correctedPath = join41(scratchDir, "audio.m4a");
94772
+ let attenuationDb = 0;
94773
+ let measuredPath = input.audioPath;
94774
+ for (let pass = 0; pass <= MAX_TRUE_PEAK_CORRECTION_PASSES; pass += 1) {
94775
+ const truePeakDbfs = await input.probeTruePeak(measuredPath, input.signal);
94776
+ if (Number.isNaN(truePeakDbfs) || truePeakDbfs === Number.POSITIVE_INFINITY) {
94777
+ return { success: false, error: "audioPadTrim: FFmpeg reported an invalid true peak" };
94778
+ }
94779
+ if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
94780
+ if (measuredPath === correctedPath) renameSync6(correctedPath, input.audioPath);
94781
+ return { success: true };
94782
+ }
94783
+ if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
94784
+ attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - truePeakDbfs;
94785
+ const result = await input.runner(
94786
+ buildAacTruePeakCorrectionArgs(
94787
+ input.audioPath,
94788
+ correctedPath,
94789
+ input.targetDurationSeconds,
94790
+ attenuationDb
94791
+ )
94792
+ );
94793
+ if (!result.success) {
94794
+ return {
94795
+ success: false,
94796
+ error: sanitizeProbeFailure(
94797
+ result.error ?? "audioPadTrim: failed to constrain AAC true peak",
94798
+ [input.audioPath, correctedPath]
94799
+ ),
94800
+ failureReason: result.failureReason
94801
+ };
94802
+ }
94803
+ measuredPath = correctedPath;
94804
+ }
94805
+ return {
94806
+ success: false,
94807
+ error: `audioPadTrim: AAC true peak remained above ${AAC_DELIVERY_TRUE_PEAK_DBFS} dBFS after ${MAX_TRUE_PEAK_CORRECTION_PASSES} correction passes`
94808
+ };
94809
+ } catch (err) {
94810
+ return {
94811
+ success: false,
94812
+ error: `audioPadTrim: failed to validate AAC true peak: ${sanitizeProbeFailure(
94813
+ err,
94814
+ scratchDir ? [input.audioPath, scratchDir] : [input.audioPath]
94815
+ )}`
94816
+ };
94817
+ } finally {
94818
+ if (scratchDir) rmSync12(scratchDir, { recursive: true, force: true });
94819
+ }
94820
+ }
94400
94821
  function failResult(outputPath, target, source, error) {
94401
94822
  return {
94402
94823
  success: false,
@@ -94470,6 +94891,22 @@ async function defaultProbeAudioInfo(audioPath, signal) {
94470
94891
  audioCodec: metadata.audioCodec
94471
94892
  };
94472
94893
  }
94894
+ async function defaultProbeAudioTruePeakDbfs(audioPath, signal) {
94895
+ const result = await runFfmpeg(
94896
+ ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", "ebur128=peak=true", "-f", "null", "-"],
94897
+ { signal }
94898
+ );
94899
+ if (!result.success) {
94900
+ throw new Error(`[audioPadTrim] ${formatFfmpegError(result.exitCode, result.stderr)}`);
94901
+ }
94902
+ const matches2 = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
94903
+ const token = matches2.at(-1)?.[1]?.toLowerCase();
94904
+ const value = token === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
94905
+ if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
94906
+ throw new Error("[audioPadTrim] FFmpeg did not report AAC true peak");
94907
+ }
94908
+ return value;
94909
+ }
94473
94910
  async function defaultRunFfmpeg(args, signal) {
94474
94911
  const result = await runFfmpeg(args, { signal });
94475
94912
  if (result.success) return { success: true };
@@ -94584,7 +95021,7 @@ function sampleDirectoryBytes(dir) {
94584
95021
  continue;
94585
95022
  }
94586
95023
  for (const name of entries2) {
94587
- const full = join41(current2, name);
95024
+ const full = join42(current2, name);
94588
95025
  try {
94589
95026
  const st = statSync10(full);
94590
95027
  if (st.isDirectory()) {
@@ -94628,7 +95065,8 @@ function summarizeExtractionObservability(extractionResult, videoCount, coverage
94628
95065
  var RenderQualityError = class extends Error {
94629
95066
  constructor(warnings) {
94630
95067
  super(
94631
- `Render blocked by ${warnings.length} correctness warning${warnings.length === 1 ? "" : "s"}: ` + warnings.map((warning) => warning.code).join(", ")
95068
+ `Render blocked by ${warnings.length} correctness warning${warnings.length === 1 ? "" : "s"}:
95069
+ ` + warnings.map((warning) => `- ${warning.code}: ${redactTelemetryString(warning.message)}`).join("\n")
94632
95070
  );
94633
95071
  this.warnings = warnings;
94634
95072
  this.name = "RenderQualityError";
@@ -94732,7 +95170,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
94732
95170
  const ranges = [];
94733
95171
  let rangeStart = null;
94734
95172
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
94735
- const framePath = join41(framesDir, formatCaptureFrameName(frameIndex, frameExt));
95173
+ const framePath = join42(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94736
95174
  const missing = !existsSync30(framePath) || statSync10(framePath).size <= 8;
94737
95175
  if (missing && rangeStart === null) {
94738
95176
  rangeStart = frameIndex;
@@ -94755,7 +95193,7 @@ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, ran
94755
95193
  workerId,
94756
95194
  startFrame: rangeStart + range.startFrame,
94757
95195
  endFrame: rangeStart + range.endFrame,
94758
- outputDir: join41(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
95196
+ outputDir: join42(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
94759
95197
  outputFrameOffset: rangeStart
94760
95198
  }));
94761
95199
  batches.push(batch);
@@ -94778,8 +95216,8 @@ function getNextRetryWorkerCount(currentWorkers) {
94778
95216
  return Math.max(1, Math.floor(currentWorkers / 2));
94779
95217
  }
94780
95218
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform3 = process.platform, systemTempDir = tmpdir7()) {
94781
- if (platform3 === "win32") return join41(systemTempDir, "hf-render-");
94782
- return join41(dirname16(outputPath), `work-${jobId}-`);
95219
+ if (platform3 === "win32") return join42(systemTempDir, "hf-render-");
95220
+ return join42(dirname17(outputPath), `work-${jobId}-`);
94783
95221
  }
94784
95222
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
94785
95223
  function captureAttemptMadeProgress(attemptTargetFrameCount, remainingFrameCount) {
@@ -94808,7 +95246,7 @@ The composition is too large for the available memory. To reduce memory pressure
94808
95246
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
94809
95247
  let captured = 0;
94810
95248
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
94811
- const framePath = join41(framesDir, formatCaptureFrameName(frameIndex, frameExt));
95249
+ const framePath = join42(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94812
95250
  if (existsSync30(framePath)) captured++;
94813
95251
  }
94814
95252
  return captured;
@@ -94833,7 +95271,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
94833
95271
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
94834
95272
  });
94835
95273
  pendingTransientRetry = false;
94836
- const attemptWorkDir = join41(options.workDir, `capture-attempt-${attempt}`);
95274
+ const attemptWorkDir = join42(options.workDir, `capture-attempt-${attempt}`);
94837
95275
  const batches = missingRanges ? buildMissingFrameRetryBatches(
94838
95276
  missingRanges,
94839
95277
  currentWorkers,
@@ -95170,15 +95608,15 @@ function deVerifyFallbackTelemetry(err) {
95170
95608
  };
95171
95609
  }
95172
95610
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal) {
95173
- const moduleDir = dirname16(fileURLToPath5(import.meta.url));
95611
+ const moduleDir = dirname17(fileURLToPath5(import.meta.url));
95174
95612
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve13(process.env.PRODUCER_RENDERS_DIR, "..") : resolve13(moduleDir, "../..");
95175
- const debugDir = join41(producerRoot, ".debug");
95176
- const outputDir = dirname16(outputPath);
95613
+ const debugDir = join42(producerRoot, ".debug");
95614
+ const outputDir = dirname17(outputPath);
95177
95615
  if (!existsSync30(outputDir)) mkdirSync17(outputDir, { recursive: true });
95178
- const workDir = job.config.debug ? join41(debugDir, job.id) : mkdtempSync7(resolveRenderWorkDirPrefix(outputPath, job.id));
95616
+ const workDir = job.config.debug ? join42(debugDir, job.id) : mkdtempSync9(resolveRenderWorkDirPrefix(outputPath, job.id));
95179
95617
  const pipelineStart = Date.now();
95180
95618
  const baseLog = job.config.logger ?? defaultLogger;
95181
- const logPath = job.config.debug ? join41(workDir, "render.log") : null;
95619
+ const logPath = job.config.debug ? join42(workDir, "render.log") : null;
95182
95620
  const execution = new RenderExecutionContext({
95183
95621
  request: { renderJobId: job.id, projectDir, outputPath },
95184
95622
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -95226,7 +95664,7 @@ async function executeRenderPipeline(input) {
95226
95664
  imageDecodeFailures: 0
95227
95665
  };
95228
95666
  let hdrPerf;
95229
- const perfOutputPath = join41(workDir, "perf-summary.json");
95667
+ const perfOutputPath = join42(workDir, "perf-summary.json");
95230
95668
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
95231
95669
  const observability = new RenderObservabilityRecorder({
95232
95670
  pipelineStartMs: pipelineStart,
@@ -95318,15 +95756,15 @@ async function executeRenderPipeline(input) {
95318
95756
  requestedWorkers: job.config.workers ?? "auto"
95319
95757
  });
95320
95758
  const entryFile = job.config.entryFile || "index.html";
95321
- let htmlPath = join41(projectDir, entryFile);
95759
+ let htmlPath = join42(projectDir, entryFile);
95322
95760
  if (!existsSync30(htmlPath)) {
95323
95761
  throw new Error(`Entry file not found: ${htmlPath}`);
95324
95762
  }
95325
95763
  assertNotAborted();
95326
95764
  const rawEntry = readFileSync12(htmlPath, "utf-8");
95327
95765
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
95328
- const wrapperPath = join41(workDir, "standalone-entry.html");
95329
- const projectIndexPath = join41(projectDir, "index.html");
95766
+ const wrapperPath = join42(workDir, "standalone-entry.html");
95767
+ const projectIndexPath = join42(projectDir, "index.html");
95330
95768
  if (!existsSync30(projectIndexPath)) {
95331
95769
  throw new Error(
95332
95770
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
@@ -95481,7 +95919,7 @@ async function executeRenderPipeline(input) {
95481
95919
  beginFrameStalled: probeResult.beginFrameStalled
95482
95920
  });
95483
95921
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
95484
- const compiledDir = join41(workDir, "compiled");
95922
+ const compiledDir = join42(workDir, "compiled");
95485
95923
  const extractResult = await observeRenderStage(
95486
95924
  observability,
95487
95925
  "video_extract",
@@ -95601,7 +96039,7 @@ async function executeRenderPipeline(input) {
95601
96039
  try {
95602
96040
  fileServer = await createFileServer2({
95603
96041
  projectDir,
95604
- compiledDir: join41(workDir, "compiled"),
96042
+ compiledDir: join42(workDir, "compiled"),
95605
96043
  port: 0,
95606
96044
  preHeadScripts: [VIRTUAL_TIME_SHIM],
95607
96045
  fps: job.config.fps
@@ -95619,7 +96057,7 @@ async function executeRenderPipeline(input) {
95619
96057
  if (!activeFileServer) {
95620
96058
  throw new Error("File server failed to initialize before frame capture");
95621
96059
  }
95622
- const framesDir = join41(workDir, "captured-frames");
96060
+ const framesDir = join42(workDir, "captured-frames");
95623
96061
  if (!existsSync30(framesDir)) mkdirSync17(framesDir, { recursive: true });
95624
96062
  const resolvedBrowserGpuMode = await resolveBrowserGpuMode(cfg.browserGpuMode, {
95625
96063
  chromePath: resolveHeadlessShellPath(cfg),
@@ -95944,7 +96382,7 @@ async function executeRenderPipeline(input) {
95944
96382
  gif: ".gif"
95945
96383
  };
95946
96384
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
95947
- const videoOnlyPath = join41(workDir, `video-only${videoExt}`);
96385
+ const videoOnlyPath = join42(workDir, `video-only${videoExt}`);
95948
96386
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
95949
96387
  if (usePageSideCompositingForTransitions) {
95950
96388
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -96537,7 +96975,7 @@ async function executeRenderPipeline(input) {
96537
96975
  }
96538
96976
  if (job.config.debug) {
96539
96977
  if (!isPngSequence && existsSync30(stagedOutputPath)) {
96540
- const debugOutput = join41(workDir, `output${videoExt}`);
96978
+ const debugOutput = join42(workDir, `output${videoExt}`);
96541
96979
  copyFileSync6(stagedOutputPath, debugOutput);
96542
96980
  }
96543
96981
  }
@@ -96875,12 +97313,12 @@ import {
96875
97313
  existsSync as existsSync33,
96876
97314
  mkdirSync as mkdirSync18,
96877
97315
  statSync as statSync12,
96878
- mkdtempSync as mkdtempSync8,
97316
+ mkdtempSync as mkdtempSync10,
96879
97317
  writeFileSync as writeFileSync11,
96880
97318
  rmSync as rmSync14,
96881
97319
  createReadStream as createReadStream5
96882
97320
  } from "fs";
96883
- import { resolve as resolve15, dirname as dirname18, join as join44 } from "path";
97321
+ import { resolve as resolve15, dirname as dirname19, join as join45 } from "path";
96884
97322
  import { tmpdir as tmpdir8 } from "os";
96885
97323
  import { parseArgs } from "util";
96886
97324
  import crypto from "crypto";
@@ -96891,7 +97329,7 @@ import { serve as serve3 } from "@hono/node-server";
96891
97329
  // ../producer/src/services/hyperframeLint.ts
96892
97330
  init_dist3();
96893
97331
  import { existsSync as existsSync31, readFileSync as readFileSync13, statSync as statSync11 } from "fs";
96894
- import { resolve as resolve14, join as join42 } from "path";
97332
+ import { resolve as resolve14, join as join43 } from "path";
96895
97333
  function isStringRecord(value) {
96896
97334
  if (!value || typeof value !== "object" || Array.isArray(value)) {
96897
97335
  return false;
@@ -96939,7 +97377,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
96939
97377
  }
96940
97378
  }
96941
97379
  return {
96942
- error: `No HTML entry file found in project directory: ${join42(absProjectDir, preferredEntryFile || "index.html")}`
97380
+ error: `No HTML entry file found in project directory: ${join43(absProjectDir, preferredEntryFile || "index.html")}`
96943
97381
  };
96944
97382
  }
96945
97383
  function prepareHyperframeLintBody(body) {
@@ -96982,7 +97420,7 @@ async function runHyperframeLint(prepared) {
96982
97420
  // ../producer/src/services/healthWorker.ts
96983
97421
  import { Worker as Worker3 } from "worker_threads";
96984
97422
  import { fileURLToPath as fileURLToPath6 } from "url";
96985
- import { dirname as dirname17, join as join43 } from "path";
97423
+ import { dirname as dirname18, join as join44 } from "path";
96986
97424
  import { existsSync as existsSync32 } from "fs";
96987
97425
  var DEFAULT_HEALTH_PORT = 9848;
96988
97426
  async function startHealthWorker(options = {}) {
@@ -97057,8 +97495,8 @@ function defaultLogger2() {
97057
97495
  };
97058
97496
  }
97059
97497
  function resolveWorkerEntry2() {
97060
- const here = dirname17(fileURLToPath6(import.meta.url));
97061
- const candidates = [join43(here, "healthWorkerThread.js"), join43(here, "healthWorkerThread.ts")];
97498
+ const here = dirname18(fileURLToPath6(import.meta.url));
97499
+ const candidates = [join44(here, "healthWorkerThread.js"), join44(here, "healthWorkerThread.ts")];
97062
97500
  for (const candidate of candidates) {
97063
97501
  if (existsSync32(candidate)) return candidate;
97064
97502
  }
@@ -97195,7 +97633,7 @@ function buildRenderJobConfig(input, outputPath, log) {
97195
97633
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
97196
97634
  const { input, cleanupProjectDir } = prepared;
97197
97635
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
97198
- const outputDir = dirname18(absoluteOutputPath);
97636
+ const outputDir = dirname19(absoluteOutputPath);
97199
97637
  if (!existsSync33(outputDir)) mkdirSync18(outputDir, { recursive: true });
97200
97638
  return { input, cleanupProjectDir, absoluteOutputPath };
97201
97639
  }
@@ -97263,8 +97701,8 @@ async function resolveInlineRenderHtml(body) {
97263
97701
  }
97264
97702
  function materializeInlineProject(html, options) {
97265
97703
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir8();
97266
- const tempProjectDir = mkdtempSync8(join44(tempRoot, "producer-project-"));
97267
- writeFileSync11(join44(tempProjectDir, "index.html"), html, "utf-8");
97704
+ const tempProjectDir = mkdtempSync10(join45(tempRoot, "producer-project-"));
97705
+ writeFileSync11(join45(tempProjectDir, "index.html"), html, "utf-8");
97268
97706
  return {
97269
97707
  prepared: {
97270
97708
  input: { projectDir: tempProjectDir, ...options },
@@ -97714,12 +98152,12 @@ init_src();
97714
98152
  // ../producer/src/services/distributed/plan.ts
97715
98153
  init_dist2();
97716
98154
  init_src();
97717
- import { cpSync as cpSync2, existsSync as existsSync36, mkdirSync as mkdirSync20, renameSync as renameSync6, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "fs";
97718
- import { join as join48, relative as relative4, sep as sep4 } from "path";
98155
+ import { cpSync as cpSync2, existsSync as existsSync36, mkdirSync as mkdirSync20, renameSync as renameSync7, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "fs";
98156
+ import { join as join49, relative as relative5, sep as sep4 } from "path";
97719
98157
 
97720
98158
  // ../producer/src/services/render/stages/freezePlan.ts
97721
98159
  import { existsSync as existsSync34, mkdirSync as mkdirSync19, readFileSync as readFileSync14, readdirSync as readdirSync12, writeFileSync as writeFileSync12 } from "fs";
97722
- import { join as join45, relative as relative2, resolve as resolve16 } from "path";
98160
+ import { join as join46, relative as relative3, resolve as resolve16 } from "path";
97723
98161
 
97724
98162
  // ../producer/src/services/distributed/planProtocol.ts
97725
98163
  var PLAN_SCHEMA_VERSION = 1;
@@ -97761,7 +98199,7 @@ var FIELD_DELIMITER = Buffer.from([0]);
97761
98199
  // ../producer/src/services/distributed/shared.ts
97762
98200
  init_src();
97763
98201
  import { execFile as execFileCallback } from "child_process";
97764
- import { dirname as dirname19, join as join46 } from "path";
98202
+ import { dirname as dirname20, join as join47 } from "path";
97765
98203
  import { existsSync as existsSync35, readFileSync as readFileSync15 } from "fs";
97766
98204
  import { fileURLToPath as fileURLToPath7 } from "url";
97767
98205
  import { promisify as promisify3 } from "util";
@@ -97770,8 +98208,8 @@ var execFile3 = promisify3(execFileCallback);
97770
98208
 
97771
98209
  // ../producer/src/services/distributed/planSize.ts
97772
98210
  import { createHash as createHash10 } from "crypto";
97773
- import { lstatSync as lstatSync4, readdirSync as readdirSync13 } from "fs";
97774
- import { extname as extname7, join as join47, relative as relative3 } from "path";
98211
+ import { lstatSync as lstatSync5, readdirSync as readdirSync13 } from "fs";
98212
+ import { extname as extname7, join as join48, relative as relative4 } from "path";
97775
98213
  var PLAN_ROOT_CATEGORY = {
97776
98214
  compiled: "compiled",
97777
98215
  [PLAN_AUDIO_RELATIVE_PATH]: "audio",
@@ -97783,9 +98221,9 @@ var PLAN_ROOT_CATEGORY = {
97783
98221
  var PLAN_DIR_SIZE_LIMIT_BYTES = 2 * 1024 * 1024 * 1024;
97784
98222
  var FREEZE_OWNED_PLAN_FILES = [
97785
98223
  "plan.json",
97786
- join48("meta", "composition.json"),
97787
- join48("meta", "encoder.json"),
97788
- join48("meta", "chunks.json")
98224
+ join49("meta", "composition.json"),
98225
+ join49("meta", "encoder.json"),
98226
+ join49("meta", "chunks.json")
97789
98227
  ];
97790
98228
 
97791
98229
  // ../producer/src/services/distributed/planV2.ts
@@ -97794,21 +98232,21 @@ import {
97794
98232
  closeSync as closeSync6,
97795
98233
  copyFileSync as copyFileSync8,
97796
98234
  existsSync as existsSync38,
97797
- lstatSync as lstatSync5,
98235
+ lstatSync as lstatSync6,
97798
98236
  mkdirSync as mkdirSync22,
97799
- mkdtempSync as mkdtempSync10,
98237
+ mkdtempSync as mkdtempSync12,
97800
98238
  openSync as openSync5,
97801
98239
  readFileSync as readFileSync16,
97802
98240
  readSync as readSync3,
97803
98241
  readdirSync as readdirSync14,
97804
- renameSync as renameSync8,
98242
+ renameSync as renameSync9,
97805
98243
  rmSync as rmSync17,
97806
98244
  statSync as statSync14,
97807
98245
  writeFileSync as writeFileSync15
97808
98246
  } from "fs";
97809
98247
  import { createHash as createHash11 } from "crypto";
97810
98248
  import { tmpdir as tmpdir9 } from "os";
97811
- import { dirname as dirname21, isAbsolute as isAbsolute4, join as join51, relative as relative5, resolve as resolve17, sep as sep5 } from "path";
98249
+ import { dirname as dirname22, isAbsolute as isAbsolute4, join as join52, relative as relative6, resolve as resolve17, sep as sep5 } from "path";
97812
98250
 
97813
98251
  // ../producer/src/services/distributed/planV2Publisher.ts
97814
98252
  import {
@@ -97816,21 +98254,21 @@ import {
97816
98254
  existsSync as existsSync37,
97817
98255
  linkSync as linkSync3,
97818
98256
  mkdirSync as mkdirSync21,
97819
- mkdtempSync as mkdtempSync9,
97820
- renameSync as renameSync7,
98257
+ mkdtempSync as mkdtempSync11,
98258
+ renameSync as renameSync8,
97821
98259
  rmSync as rmSync16,
97822
98260
  statSync as statSync13,
97823
98261
  writeFileSync as writeFileSync14
97824
98262
  } from "fs";
97825
- import { dirname as dirname20, join as join50 } from "path";
98263
+ import { dirname as dirname21, join as join51 } from "path";
97826
98264
 
97827
98265
  // ../producer/src/services/distributed/planV2Layout.ts
97828
- import { join as join49 } from "path";
98266
+ import { join as join50 } from "path";
97829
98267
 
97830
98268
  // ../producer/src/services/distributed/planV2Execution.ts
97831
- import { mkdtempSync as mkdtempSync11, rmSync as rmSync20 } from "fs";
98269
+ import { mkdtempSync as mkdtempSync13, rmSync as rmSync20 } from "fs";
97832
98270
  import { tmpdir as tmpdir10 } from "os";
97833
- import { join as join54 } from "path";
98271
+ import { join as join55 } from "path";
97834
98272
 
97835
98273
  // ../producer/src/services/distributed/assemble.ts
97836
98274
  init_src();
@@ -97845,19 +98283,19 @@ import {
97845
98283
  statSync as statSync15,
97846
98284
  writeFileSync as writeFileSync16
97847
98285
  } from "fs";
97848
- import { dirname as dirname22, join as join52 } from "path";
98286
+ import { dirname as dirname23, join as join53 } from "path";
97849
98287
 
97850
98288
  // ../producer/src/services/distributed/renderChunk.ts
97851
98289
  init_src();
97852
98290
  import { randomBytes as randomBytes2 } from "crypto";
97853
98291
  import { existsSync as existsSync40, mkdirSync as mkdirSync24, readFileSync as readFileSync18, readdirSync as readdirSync16, rmSync as rmSync19, writeFileSync as writeFileSync17 } from "fs";
97854
- import { extname as extname8, join as join53 } from "path";
98292
+ import { extname as extname8, join as join54 } from "path";
97855
98293
  init_src();
97856
98294
 
97857
98295
  // ../producer/src/services/distributed/projectHash.ts
97858
98296
  import { readdirSync as readdirSync17, readFileSync as readFileSync19 } from "fs";
97859
98297
  import { createHash as createHash12 } from "crypto";
97860
- import { join as join55, relative as relative6 } from "path";
98298
+ import { join as join56, relative as relative7 } from "path";
97861
98299
 
97862
98300
  // src/fontLocalize.ts
97863
98301
  function safeVersion(version2) {
@@ -97898,8 +98336,8 @@ async function runFontLocalize(io, localize) {
97898
98336
  }
97899
98337
 
97900
98338
  // src/version.ts
97901
- var VERSION = true ? "0.8.26" : "0.0.0-dev";
97902
- var PRODUCER_VERSION = true ? "0.8.26" : "0.0.0-dev";
98339
+ var VERSION = true ? "0.8.28" : "0.0.0-dev";
98340
+ var PRODUCER_VERSION = true ? "0.8.28" : "0.0.0-dev";
97903
98341
 
97904
98342
  // src/fontLocalizeCli.ts
97905
98343
  async function readStdin() {