hyperframes 0.8.78 → 0.8.80

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.
Files changed (42) hide show
  1. package/dist/cli.js +2958 -1893
  2. package/dist/fontLocalizeCli.js +160 -54
  3. package/dist/hyperframe-runtime.js +57 -57
  4. package/dist/hyperframe.manifest.json +1 -1
  5. package/dist/hyperframe.runtime.iife.js +57 -57
  6. package/dist/hyperframes-player.global.js +3 -3
  7. package/dist/registry/localSemantic.js +6 -25
  8. package/dist/skills/media-use/audio/references/remove-background.md +1 -1
  9. package/dist/skills/media-use/registry/localSemantic.js +6 -25
  10. package/dist/skills/media-use/scripts/compatibility.test.mjs +2 -0
  11. package/dist/skills/media-use/scripts/lib/cache.mjs +6 -8
  12. package/dist/skills/media-use/scripts/lib/candidates.test.mjs +2 -2
  13. package/dist/skills/media-use/scripts/lib/config-lock.d.mts +3 -0
  14. package/dist/skills/media-use/scripts/lib/config-lock.mjs +45 -0
  15. package/dist/skills/media-use/scripts/lib/config-lock.test.mjs +79 -0
  16. package/dist/skills/media-use/scripts/lib/manifest.test.mjs +2 -0
  17. package/dist/skills/media-use/scripts/lib/media-home.mjs +20 -0
  18. package/dist/skills/media-use/scripts/lib/media-home.test.mjs +61 -0
  19. package/dist/skills/media-use/scripts/lib/misses.mjs +6 -6
  20. package/dist/skills/media-use/scripts/lib/misses.test.mjs +12 -1
  21. package/dist/skills/media-use/scripts/lib/prefs-store.mjs +2 -2
  22. package/dist/skills/media-use/scripts/lib/prefs-store.test.mjs +1 -1
  23. package/dist/skills/media-use/scripts/lib/recipe-store.mjs +2 -2
  24. package/dist/skills/media-use/scripts/lib/recipe-store.test.mjs +1 -1
  25. package/dist/skills/media-use/scripts/lib/stats.test.mjs +1 -1
  26. package/dist/skills/media-use/scripts/lib/telemetry.mjs +26 -9
  27. package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +64 -0
  28. package/dist/skills/registry/localSemantic.js +6 -25
  29. package/dist/studio/assets/hyperframes-player-CgnoxDoD.js +464 -0
  30. package/dist/studio/assets/{index-CX_4MYbk.js → index-CsG24Jub.js} +190 -187
  31. package/dist/studio/assets/{index-C_n__Uh3.js → index-DZGVvqDT.js} +1 -1
  32. package/dist/studio/assets/{index-DdpuHaGN.js → index-J9HXe7mU.js} +1 -1
  33. package/dist/studio/{chunk-5VANA2TG.js → chunk-I7XNAIOF.js} +2 -1
  34. package/dist/studio/{domEditingLayers-IGPLTI5E.js → domEditingLayers-MHFDKAQ7.js} +2 -2
  35. package/dist/studio/index.d.ts +40 -16
  36. package/dist/studio/index.html +1 -1
  37. package/dist/studio/index.js +1918 -1408
  38. package/dist/studio/index.js.map +1 -1
  39. package/package.json +1 -1
  40. package/dist/studio/assets/hyperframes-player-BdnQGwG5.js +0 -464
  41. /package/dist/studio/{chunk-5VANA2TG.js.map → chunk-I7XNAIOF.js.map} +0 -0
  42. /package/dist/studio/{domEditingLayers-IGPLTI5E.js.map → domEditingLayers-MHFDKAQ7.js.map} +0 -0
@@ -52710,9 +52710,11 @@ var init_subCompositionValidity2 = __esm({
52710
52710
  });
52711
52711
 
52712
52712
  // ../core/dist/compiler/htmlDocument.js
52713
+ function isFullHtmlDocument(html) {
52714
+ return /^(?:<!doctype|<html[\s>/])/i.test(html.replace(LEADING_COMMENTS, ""));
52715
+ }
52713
52716
  function parseHTMLContent(html) {
52714
- const trimmed = html.trimStart().toLowerCase();
52715
- if (trimmed.startsWith("<!doctype") || trimmed.startsWith("<html")) {
52717
+ if (isFullHtmlDocument(html)) {
52716
52718
  return parseHTML(html).document;
52717
52719
  }
52718
52720
  return parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`).document;
@@ -52965,7 +52967,7 @@ ${bodyTags}`;
52965
52967
  }
52966
52968
  return html;
52967
52969
  }
52968
- var RUNTIME_BOOTSTRAP_ATTR, RUNTIME_SRC_MARKERS, RUNTIME_INLINE_MARKERS, SIMPLE_RUNTIME_FLAG_ASSIGNMENTS, RAW_TEXT_TAGS, COMMENT_END;
52970
+ var RUNTIME_BOOTSTRAP_ATTR, RUNTIME_SRC_MARKERS, RUNTIME_INLINE_MARKERS, SIMPLE_RUNTIME_FLAG_ASSIGNMENTS, LEADING_COMMENTS, RAW_TEXT_TAGS, COMMENT_END;
52969
52971
  var init_htmlDocument = __esm({
52970
52972
  "../core/dist/compiler/htmlDocument.js"() {
52971
52973
  "use strict";
@@ -52988,6 +52990,7 @@ var init_htmlDocument = __esm({
52988
52990
  /^window\.__playerReady\s*=\s*(?:true|false)\s*;?$/,
52989
52991
  /^window\.__renderReady\s*=\s*(?:true|false)\s*;?$/
52990
52992
  ];
52993
+ LEADING_COMMENTS = /^(?:\s|<!--(?:>|->|[\s\S]*?-->))*/;
52991
52994
  RAW_TEXT_TAGS = ["script", "style", "title", "textarea"];
52992
52995
  COMMENT_END = /--!?>/g;
52993
52996
  }
@@ -54096,7 +54099,20 @@ function quantizeSeekTime(timeSeconds, fps, subFrameDivisions) {
54096
54099
  const grid = safeFps(fps) * divisions;
54097
54100
  return Math.round(safeTime(timeSeconds) * grid) / grid;
54098
54101
  }
54099
- var MEDIA_VISUAL_STYLE_PROPERTIES;
54102
+ function snapTimeToFrameBoundary(timeSeconds, fps) {
54103
+ const grid = safeFps(fps);
54104
+ const time = safeTime(timeSeconds);
54105
+ const framePosition = time * grid;
54106
+ const nearestFrame = Math.round(framePosition);
54107
+ return Math.abs(framePosition - nearestFrame) <= FRAME_BOUNDARY_EPSILON ? nearestFrame / grid : time;
54108
+ }
54109
+ function exportClipWindow(start, end, fps) {
54110
+ return {
54111
+ start: snapTimeToFrameBoundary(start, fps),
54112
+ end: Number.isFinite(end) ? snapTimeToFrameBoundary(end, fps) : end
54113
+ };
54114
+ }
54115
+ var MEDIA_VISUAL_STYLE_PROPERTIES, FRAME_BOUNDARY_EPSILON;
54100
54116
  var init_parityContract = __esm({
54101
54117
  "../core/dist/inline-scripts/parityContract.js"() {
54102
54118
  "use strict";
@@ -54131,6 +54147,7 @@ var init_parityContract = __esm({
54131
54147
  "scale",
54132
54148
  "box-sizing"
54133
54149
  ];
54150
+ FRAME_BOUNDARY_EPSILON = 1e-3;
54134
54151
  }
54135
54152
  });
54136
54153
 
@@ -54440,10 +54457,28 @@ var init_startResolver = __esm({
54440
54457
  }
54441
54458
  });
54442
54459
 
54460
+ // ../core/dist/clipFacts.js
54461
+ function sameInstant(a, b) {
54462
+ if (!Number.isFinite(a) || !Number.isFinite(b))
54463
+ return a === b;
54464
+ return Math.abs(a - b) <= instantTolerance(Math.max(Math.abs(a), Math.abs(b)));
54465
+ }
54466
+ var instantTolerance;
54467
+ var init_clipFacts = __esm({
54468
+ "../core/dist/clipFacts.js"() {
54469
+ "use strict";
54470
+ instantTolerance = (time) => 4 * Number.EPSILON * Math.max(1, Math.abs(time));
54471
+ }
54472
+ });
54473
+
54443
54474
  // ../core/dist/runtime/clipWindow.js
54475
+ var hasClipStarted, isInClipWindow;
54444
54476
  var init_clipWindow = __esm({
54445
54477
  "../core/dist/runtime/clipWindow.js"() {
54446
54478
  "use strict";
54479
+ init_clipFacts();
54480
+ hasClipStarted = (time, start) => time >= start || sameInstant(time, start);
54481
+ isInClipWindow = (time, start, end) => hasClipStarted(time, start) && time < end && !sameInstant(time, end);
54447
54482
  }
54448
54483
  });
54449
54484
 
@@ -66236,6 +66271,16 @@ var init_scriptRuns = __esm({
66236
66271
  }
66237
66272
  });
66238
66273
 
66274
+ // ../core/dist/sceneParts.js
66275
+ var SCENE_PART_ATTR, SCENE_NO_SWAP_ATTR;
66276
+ var init_sceneParts = __esm({
66277
+ "../core/dist/sceneParts.js"() {
66278
+ "use strict";
66279
+ SCENE_PART_ATTR = "data-hf-scene";
66280
+ SCENE_NO_SWAP_ATTR = "data-hf-scene-no-swap";
66281
+ }
66282
+ });
66283
+
66239
66284
  // ../core/dist/compiler/externalScripts.js
66240
66285
  function readExternalScriptAttributes(el) {
66241
66286
  const attributes2 = {};
@@ -66625,6 +66670,13 @@ var init_composition = __esm({
66625
66670
  }
66626
66671
  });
66627
66672
 
66673
+ // ../core/dist/runtime/proxySrc.js
66674
+ var init_proxySrc = __esm({
66675
+ "../core/dist/runtime/proxySrc.js"() {
66676
+ "use strict";
66677
+ }
66678
+ });
66679
+
66628
66680
  // ../core/dist/runtime/applyVariableBindings.js
66629
66681
  function sanitizeCssValue(value) {
66630
66682
  return value.replace(/[;{}<>\r\n]/g, "");
@@ -66634,6 +66686,7 @@ var init_applyVariableBindings = __esm({
66634
66686
  "use strict";
66635
66687
  init_variableScope();
66636
66688
  init_domRealm();
66689
+ init_proxySrc();
66637
66690
  }
66638
66691
  });
66639
66692
 
@@ -78918,13 +78971,18 @@ var init_compositionAssembly = __esm({
78918
78971
  });
78919
78972
 
78920
78973
  // ../core/dist/compiler/inlineSubCompositions.js
78974
+ function sceneScriptSwapRefusal(script) {
78975
+ const match = SIDE_EFFECT_RE.exec(script);
78976
+ return match ? `its script uses ${match[0].trim()}` : null;
78977
+ }
78921
78978
  function defaultBuildScopeSelector(compId) {
78922
78979
  const escaped = compId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
78923
78980
  return `[data-composition-id="${escaped}"]`;
78924
78981
  }
78925
78982
  function inlineSubCompositions(document2, hosts, options) {
78926
- const { resolveHtml, parseHtml, hostIdentityMap, rewriteInlineStyles = false, flattenInnerRoot, compoundAuthoredRoot, readVariableDefaults, parseHostVariables, buildScopeSelector = defaultBuildScopeSelector, scriptErrorLabel = "[HyperFrames] composition script error:", onMissingComposition, assetExists } = options;
78983
+ const { resolveHtml, parseHtml, hostIdentityMap, rewriteInlineStyles = false, flattenInnerRoot, compoundAuthoredRoot, readVariableDefaults, parseHostVariables, buildScopeSelector = defaultBuildScopeSelector, scriptErrorLabel = "[HyperFrames] composition script error:", onMissingComposition, assetExists, tagScenes = false } = options;
78927
78984
  const styles = [];
78985
+ const styleScenes = [];
78928
78986
  const scripts = [];
78929
78987
  const externalScriptSrcs = [];
78930
78988
  const scriptItems = [];
@@ -78933,9 +78991,14 @@ function inlineSubCompositions(document2, hosts, options) {
78933
78991
  const externalLinks = [];
78934
78992
  const seenLinkHrefs = /* @__PURE__ */ new Set();
78935
78993
  const variablesByComp = {};
78936
- const queue = hosts.map((element) => ({ element, ancestry: [] }));
78994
+ const sceneHosts = /* @__PURE__ */ new Map();
78995
+ const queue = hosts.map((element) => ({
78996
+ element,
78997
+ ancestry: [],
78998
+ scene: void 0
78999
+ }));
78937
79000
  for (let queueIndex = 0; queueIndex < queue.length; queueIndex += 1) {
78938
- const { element: hostEl, ancestry } = queue[queueIndex];
79001
+ const { element: hostEl, ancestry, scene: parentScene } = queue[queueIndex];
78939
79002
  const src = hostEl.getAttribute("data-composition-src");
78940
79003
  if (!src)
78941
79004
  continue;
@@ -78982,6 +79045,19 @@ function inlineSubCompositions(document2, hosts, options) {
78982
79045
  const scopeCompId = plan2.authoredCompositionId || "";
78983
79046
  const scriptCompositionId = plan2.scriptCompositionId || "";
78984
79047
  const runtimeScope = runtimeCompId ? buildScopeSelector(runtimeCompId) : "";
79048
+ const scene = tagScenes ? parentScene ?? (runtimeCompId || src) : void 0;
79049
+ if (scene && !parentScene) {
79050
+ hostEl.setAttribute(SCENE_PART_ATTR, scene);
79051
+ sceneHosts.set(scene, hostEl);
79052
+ }
79053
+ const refuseSwap = (why) => {
79054
+ const host = scene ? sceneHosts.get(scene) : void 0;
79055
+ if (!host || host.hasAttribute(SCENE_NO_SWAP_ATTR))
79056
+ return;
79057
+ const reason = why();
79058
+ if (reason)
79059
+ host.setAttribute(SCENE_NO_SWAP_ATTR, reason);
79060
+ };
78985
79061
  if (readVariableDefaults && parseHostVariables && runtimeCompId) {
78986
79062
  const mergedVariables = {};
78987
79063
  for (const carrier of plan2.variableDefaultCarriers) {
@@ -79013,11 +79089,14 @@ function inlineSubCompositions(document2, hosts, options) {
79013
79089
  }
79014
79090
  for (const styleEl of plan2.styleSources) {
79015
79091
  styles.push(scopeSubStyle(styleEl.textContent || ""));
79092
+ if (scene)
79093
+ styleScenes.push(scene);
79016
79094
  styleEl.remove();
79017
79095
  }
79018
79096
  for (const scriptEl of plan2.scriptSources) {
79019
79097
  const externalSrc = resolveSubAssetPath(scriptEl.getAttribute("src"));
79020
79098
  const type = (scriptEl.getAttribute("type") || "").trim().toLowerCase();
79099
+ refuseSwap(() => type === "importmap" || type === "module" ? "it runs a module script or import map" : !externalSrc ? sceneScriptSwapRefusal(scriptEl.textContent || "") : isSwapSafeLibrary(externalSrc) ? null : "it runs a script that is not a known library");
79021
79100
  if (type === "importmap") {
79022
79101
  const map = parseImportMap(scriptEl.textContent || "", (url) => {
79023
79102
  const authored = url.trim();
@@ -79041,12 +79120,13 @@ function inlineSubCompositions(document2, hosts, options) {
79041
79120
  scriptItems.push({
79042
79121
  kind: "external",
79043
79122
  src: externalSrc,
79044
- ...readExternalScriptAttributes(scriptEl)
79123
+ ...readExternalScriptAttributes(scriptEl),
79124
+ ...scene ? { scene } : {}
79045
79125
  });
79046
79126
  } else {
79047
79127
  const wrappedScript = scriptCompositionId ? wrapScopedCompositionScript(scriptEl.textContent || "", scriptCompositionId, scriptErrorLabel, runtimeScope || void 0, runtimeCompId || scopeCompId || scriptCompositionId, authoredRootId, src) : wrapInlineScriptWithErrorBoundary(scriptEl.textContent || "", scriptErrorLabel);
79048
79128
  scripts.push(wrappedScript);
79049
- scriptItems.push({ kind: "inline", content: wrappedScript });
79129
+ scriptItems.push({ kind: "inline", content: wrappedScript, ...scene ? { scene } : {} });
79050
79130
  }
79051
79131
  scriptEl.remove();
79052
79132
  }
@@ -79102,11 +79182,12 @@ function inlineSubCompositions(document2, hosts, options) {
79102
79182
  onMissingComposition?.(skipped.src, skipped.reason);
79103
79183
  }
79104
79184
  for (const nestedHost of nested.hosts) {
79105
- queue.push({ element: nestedHost.host, ancestry: nestedAncestry });
79185
+ queue.push({ element: nestedHost.host, ancestry: nestedAncestry, scene });
79106
79186
  }
79107
79187
  }
79108
79188
  return {
79109
79189
  styles,
79190
+ styleScenes,
79110
79191
  scripts,
79111
79192
  externalScriptSrcs,
79112
79193
  scriptItems,
@@ -79116,6 +79197,7 @@ function inlineSubCompositions(document2, hosts, options) {
79116
79197
  moduleScripts
79117
79198
  };
79118
79199
  }
79200
+ var SIDE_EFFECT_RE, SWAP_SAFE_LIBRARY_URL, isSwapSafeLibrary;
79119
79201
  var init_inlineSubCompositions = __esm({
79120
79202
  "../core/dist/compiler/inlineSubCompositions.js"() {
79121
79203
  "use strict";
@@ -79126,10 +79208,15 @@ var init_inlineSubCompositions = __esm({
79126
79208
  init_compositionScoping();
79127
79209
  init_subCompositionValidity();
79128
79210
  init_compositionAssembly();
79211
+ init_sceneParts();
79212
+ SIDE_EFFECT_RE = /\b(addEventListener|requestAnimationFrame|requestIdleCallback|setTimeout|setInterval|queueMicrotask|getContext|WebGL\w*|WebGPU\w*|gpu|Worker|WebSocket|EventSource|Audio\w*|\w*Observer|fetch|import|eval|Function|Promise|async|await|delayedCall|ScrollTrigger|Draggable|anime|customElements|registerProperty|addListener|BroadcastChannel|pushState|replaceState|adoptedStyleSheets|documentElement|getElementsByTagName|lottie|THREE|__hf[A-Z]\w*)\b|\.then\s*\(|\.animate\s*\(|\.ticker\b|repeat\s*:\s*-1|\.repeat\s*\(\s*-1|defineProperty\s*\(\s*(window|globalThis|self|document)\b|\bfonts\s*\.\s*add\b|\.on[a-z]+\s*=(?!=)|\bon(resize|scroll|message|key\w+|click|pointer\w+|mouse\w+|wheel|visibilitychange|hashchange|popstate|error|load)\s*=(?!=)|\[\s*["']on[a-z]+["']\s*\]|document\s*\.\s*(head|body)\b|querySelector(All)?\(\s*["'](head|body)["']|\bgsap\s*\.\s*(?:to|from|fromTo)\s*\(|\bgsap\s*\.\s*timeline\s*\((?:[^()]|\([^()]*\))*\)\s*\.\s*(?:to|from|fromTo|set|add|call)\s*\(/;
79213
+ SWAP_SAFE_LIBRARY_URL = /^https:\/\/(cdn\.jsdelivr\.net\/npm|unpkg\.com)\/(gsap|three|d3|d3-[a-z-]+|topojson-client|clipper-lib)(@[^/]+)?\//;
79214
+ isSwapSafeLibrary = (src) => URL.canParse(src) && SWAP_SAFE_LIBRARY_URL.test(new URL(src).href);
79129
79215
  }
79130
79216
  });
79131
79217
 
79132
79218
  // ../core/dist/compiler/htmlBundler.js
79219
+ import { AsyncLocalStorage } from "async_hooks";
79133
79220
  import { transformSync } from "esbuild";
79134
79221
  function cssAttributeSelector(attr, value) {
79135
79222
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/</g, "\\3c ");
@@ -79306,11 +79393,12 @@ function declarerVariableRules(document2, layerFor) {
79306
79393
  }
79307
79394
  return rules;
79308
79395
  }
79309
- var MAX_INLINE_ASSET_BYTES;
79396
+ var bundleReads, MAX_INLINE_ASSET_BYTES;
79310
79397
  var init_htmlBundler = __esm({
79311
79398
  "../core/dist/compiler/htmlBundler.js"() {
79312
79399
  "use strict";
79313
79400
  init_scriptRuns();
79401
+ init_sceneParts();
79314
79402
  init_externalScripts();
79315
79403
  init_importMaps();
79316
79404
  init_flattenedRoot();
@@ -79329,6 +79417,7 @@ var init_htmlBundler = __esm({
79329
79417
  init_cssSelector();
79330
79418
  init_safePath();
79331
79419
  init_colorGrading();
79420
+ bundleReads = new AsyncLocalStorage();
79332
79421
  MAX_INLINE_ASSET_BYTES = 2 * 1024 * 1024;
79333
79422
  }
79334
79423
  });
@@ -79341,6 +79430,16 @@ var init_htmlParityContract = __esm({
79341
79430
  }
79342
79431
  });
79343
79432
 
79433
+ // ../core/dist/compiler/scenePartsManifest.js
79434
+ import { createHash as createHash2 } from "crypto";
79435
+ var init_scenePartsManifest = __esm({
79436
+ "../core/dist/compiler/scenePartsManifest.js"() {
79437
+ "use strict";
79438
+ init_sceneParts();
79439
+ init_htmlDocument();
79440
+ }
79441
+ });
79442
+
79344
79443
  // ../core/dist/compiler/index.js
79345
79444
  var init_compiler = __esm({
79346
79445
  "../core/dist/compiler/index.js"() {
@@ -79352,6 +79451,7 @@ var init_compiler = __esm({
79352
79451
  init_getVariables();
79353
79452
  init_htmlParityContract();
79354
79453
  init_htmlDocument();
79454
+ init_scenePartsManifest();
79355
79455
  init_scriptRuns();
79356
79456
  init_staticGuard();
79357
79457
  init_compositionScoping();
@@ -79455,7 +79555,7 @@ var init_extractedFrameIndex = __esm({
79455
79555
  });
79456
79556
 
79457
79557
  // ../engine/src/services/extractionCache.ts
79458
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
79558
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
79459
79559
  import {
79460
79560
  existsSync as existsSync10,
79461
79561
  lstatSync as lstatSync3,
@@ -79491,7 +79591,7 @@ function canonicalKeyBlob(input) {
79491
79591
  return JSON.stringify(blob);
79492
79592
  }
79493
79593
  function computeCacheKey(input) {
79494
- return createHash2("sha256").update(canonicalKeyBlob(input)).digest("hex");
79594
+ return createHash3("sha256").update(canonicalKeyBlob(input)).digest("hex");
79495
79595
  }
79496
79596
  function cacheEntryDirName(keyHash) {
79497
79597
  return SCHEMA_PREFIX + keyHash.slice(0, KEY_HEX_CHARS);
@@ -81015,8 +81115,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
81015
81115
  };
81016
81116
  }
81017
81117
  function getFrameIndexAtTime(extracted, globalTime, videoStart, loop = false, mediaStart = 0, holdLastFrame = false, playbackRate = 1) {
81018
- let localTime = globalTime - videoStart;
81019
- if (localTime < 0) return null;
81118
+ let localTime = Math.max(0, globalTime - videoStart);
81020
81119
  const normalizedPlaybackRate = normalizeRateSpec(playbackRate);
81021
81120
  const loopDuration = timeAtSourceTime(
81022
81121
  normalizedPlaybackRate,
@@ -81038,6 +81137,7 @@ function getFrameIndexAtTime(extracted, globalTime, videoStart, loop = false, me
81038
81137
  return frameIndex;
81039
81138
  }
81040
81139
  function getFrameAtTime(extracted, globalTime, videoStart, loop = false, mediaStart = 0) {
81140
+ if (!hasClipStarted(globalTime, videoStart)) return null;
81041
81141
  const frameIndex = getFrameIndexAtTime(extracted, globalTime, videoStart, loop, mediaStart);
81042
81142
  return frameIndex == null ? null : extracted.framePaths.get(frameIndex) || null;
81043
81143
  }
@@ -81050,8 +81150,8 @@ function analyzeClipMediaFit(params) {
81050
81150
  if (shortfallSeconds <= toleranceSeconds) return null;
81051
81151
  return { shortfallSeconds, toleranceSeconds };
81052
81152
  }
81053
- function createFrameLookupTable(videos, extracted) {
81054
- const table = new FrameLookupTable();
81153
+ function createFrameLookupTable(videos, extracted, fps) {
81154
+ const table = new FrameLookupTable(fps);
81055
81155
  const extractedMap = /* @__PURE__ */ new Map();
81056
81156
  for (const ext of extracted) extractedMap.set(ext.videoId, ext);
81057
81157
  for (const video of videos) {
@@ -81102,6 +81202,11 @@ var init_videoFrameExtractor = __esm({
81102
81202
  FINAL_FRAME_LOGICAL_DURATION_SECONDS = 1e-6;
81103
81203
  ALPHA_CAPABLE_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8", "prores"]);
81104
81204
  FrameLookupTable = class {
81205
+ /** The render's `fps`, so a clip shows in exactly export visibility's frames; omit for authored times. */
81206
+ constructor(fps) {
81207
+ this.fps = fps;
81208
+ }
81209
+ fps;
81105
81210
  videos = /* @__PURE__ */ new Map();
81106
81211
  orderedVideos = [];
81107
81212
  activeVideoIds = /* @__PURE__ */ new Set();
@@ -81111,18 +81216,18 @@ var init_videoFrameExtractor = __esm({
81111
81216
  this.videos.set(extracted.videoId, {
81112
81217
  extracted,
81113
81218
  start,
81114
- end,
81219
+ clipWindow: this.fps ? exportClipWindow(start, end, this.fps) : { start, end },
81115
81220
  mediaStart,
81116
81221
  loop,
81117
81222
  playbackRate: normalizeRateSpec(playbackRate)
81118
81223
  });
81119
- this.orderedVideos = Array.from(this.videos.entries()).map(([videoId, video]) => ({ videoId, ...video })).sort((a, b) => a.start - b.start);
81224
+ this.orderedVideos = Array.from(this.videos.entries()).map(([videoId, video]) => ({ videoId, ...video })).sort((a, b) => a.clipWindow.start - b.clipWindow.start);
81120
81225
  this.resetActiveState();
81121
81226
  }
81122
81227
  getFrame(videoId, globalTime) {
81123
81228
  const video = this.videos.get(videoId);
81124
81229
  if (!video) return null;
81125
- if (globalTime < video.start || globalTime > video.end) return null;
81230
+ if (!isInClipWindow(globalTime, video.clipWindow.start, video.clipWindow.end)) return null;
81126
81231
  const frameIndex = getFrameIndexAtTime(
81127
81232
  video.extracted,
81128
81233
  globalTime,
@@ -81144,14 +81249,11 @@ var init_videoFrameExtractor = __esm({
81144
81249
  this.activeVideoIds.clear();
81145
81250
  this.startCursor = 0;
81146
81251
  for (const entry of this.orderedVideos) {
81147
- if (entry.start <= globalTime && globalTime <= entry.end) {
81252
+ if (!hasClipStarted(globalTime, entry.clipWindow.start)) break;
81253
+ if (isInClipWindow(globalTime, entry.clipWindow.start, entry.clipWindow.end)) {
81148
81254
  this.activeVideoIds.add(entry.videoId);
81149
81255
  }
81150
- if (entry.start <= globalTime) {
81151
- this.startCursor += 1;
81152
- } else {
81153
- break;
81154
- }
81256
+ this.startCursor += 1;
81155
81257
  }
81156
81258
  this.lastTime = globalTime;
81157
81259
  return;
@@ -81159,17 +81261,17 @@ var init_videoFrameExtractor = __esm({
81159
81261
  while (this.startCursor < this.orderedVideos.length) {
81160
81262
  const candidate = this.orderedVideos[this.startCursor];
81161
81263
  if (!candidate) break;
81162
- if (candidate.start > globalTime) {
81264
+ if (!hasClipStarted(globalTime, candidate.clipWindow.start)) {
81163
81265
  break;
81164
81266
  }
81165
- if (globalTime <= candidate.end) {
81267
+ if (isInClipWindow(globalTime, candidate.clipWindow.start, candidate.clipWindow.end)) {
81166
81268
  this.activeVideoIds.add(candidate.videoId);
81167
81269
  }
81168
81270
  this.startCursor += 1;
81169
81271
  }
81170
81272
  for (const videoId of Array.from(this.activeVideoIds)) {
81171
81273
  const video = this.videos.get(videoId);
81172
- if (!video || globalTime < video.start || globalTime > video.end) {
81274
+ if (!video || !isInClipWindow(globalTime, video.clipWindow.start, video.clipWindow.end)) {
81173
81275
  this.activeVideoIds.delete(videoId);
81174
81276
  }
81175
81277
  }
@@ -81658,10 +81760,10 @@ var init_videoFrameInjector = __esm({
81658
81760
  });
81659
81761
 
81660
81762
  // ../engine/src/utils/notMediaPayload.ts
81661
- import { createHash as createHash3 } from "crypto";
81763
+ import { createHash as createHash4 } from "crypto";
81662
81764
  import { open as openFile } from "fs/promises";
81663
81765
  function fingerprintElementId(elementId) {
81664
- return createHash3("sha256").update(elementId).digest("hex").slice(0, 16);
81766
+ return createHash4("sha256").update(elementId).digest("hex").slice(0, 16);
81665
81767
  }
81666
81768
  function byteOrderMarkLength(head) {
81667
81769
  for (const mark of BYTE_ORDER_MARKS) {
@@ -88688,7 +88790,7 @@ __export(fontCompression_exports, {
88688
88790
  compressToWoff2: () => compressToWoff2,
88689
88791
  fontToDataUri: () => fontToDataUri
88690
88792
  });
88691
- import { createHash as createHash7 } from "crypto";
88793
+ import { createHash as createHash8 } from "crypto";
88692
88794
  import {
88693
88795
  existsSync as existsSync23,
88694
88796
  mkdirSync as mkdirSync13,
@@ -88711,7 +88813,7 @@ function defaultCacheDir() {
88711
88813
  return join32(root, "local-compression-v1");
88712
88814
  }
88713
88815
  function cachedCompressionPath(input, originalFormat, cacheDir) {
88714
- const digest = createHash7("sha256").update("hyperframes-local-font-compression-v1\0").update(originalFormat).update("\0").update(input).digest("hex");
88816
+ const digest = createHash8("sha256").update("hyperframes-local-font-compression-v1\0").update(originalFormat).update("\0").update(input).digest("hex");
88715
88817
  return join32(cacheDir, `${digest}.woff2`);
88716
88818
  }
88717
88819
  function readCachedCompression(path) {
@@ -88822,7 +88924,7 @@ import { Readable as Readable2 } from "stream";
88822
88924
  import { join as join21, extname as extname4, resolve as resolve7, sep as sep3 } from "path";
88823
88925
 
88824
88926
  // ../producer/src/services/hyperframeRuntimeLoader.ts
88825
- import { createHash as createHash4 } from "crypto";
88927
+ import { createHash as createHash5 } from "crypto";
88826
88928
  import { existsSync as existsSync16, readFileSync as readFileSync8 } from "fs";
88827
88929
  import { dirname as dirname11, resolve as resolve6 } from "path";
88828
88930
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -88874,7 +88976,7 @@ function resolveVerifiedHyperframeRuntime() {
88874
88976
  throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
88875
88977
  }
88876
88978
  const runtimeSource = readFileSync8(runtimePath, "utf8");
88877
- const runtimeSha = createHash4("sha256").update(runtimeSource, "utf8").digest("hex");
88979
+ const runtimeSha = createHash5("sha256").update(runtimeSource, "utf8").digest("hex");
88878
88980
  if (runtimeSha !== manifest.sha256) {
88879
88981
  throw new Error(
88880
88982
  `[HyperframeRuntimeLoader] Runtime checksum mismatch. expected=${manifest.sha256} actual=${runtimeSha}`
@@ -92801,7 +92903,7 @@ async function runCaptureSegmentedStage(input) {
92801
92903
  }
92802
92904
 
92803
92905
  // ../producer/src/services/render/segmentManifest.ts
92804
- import { createHash as createHash5 } from "crypto";
92906
+ import { createHash as createHash6 } from "crypto";
92805
92907
  import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync10, renameSync as renameSync4, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
92806
92908
  import { join as join29 } from "path";
92807
92909
  var MANIFEST_FILENAME = "segments.json";
@@ -92823,7 +92925,7 @@ function computeSegmentPlanHash(input) {
92823
92925
  `${input.outputWidth}x${input.outputHeight}`,
92824
92926
  input.motionBlur
92825
92927
  ].join(" ");
92826
- return createHash5("sha256").update(ordered).digest("hex").slice(0, 16);
92928
+ return createHash6("sha256").update(ordered).digest("hex").slice(0, 16);
92827
92929
  }
92828
92930
  function segmentDirFor(projectRendersDir, planHash) {
92829
92931
  return join29(projectRendersDir, ".hf-segments", planHash);
@@ -93266,7 +93368,7 @@ function shouldFallbackToScreenshotAfterCalibrationError(error) {
93266
93368
 
93267
93369
  // ../producer/src/services/render/observability.ts
93268
93370
  init_dist2();
93269
- import { createHash as createHash6 } from "crypto";
93371
+ import { createHash as createHash7 } from "crypto";
93270
93372
  var MAX_EVENTS = 160;
93271
93373
  var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
93272
93374
  "browserGpuMode",
@@ -93311,7 +93413,7 @@ function sanitizeObservationMessage(value) {
93311
93413
  return redactTelemetryString(value);
93312
93414
  }
93313
93415
  function computeCompositionObservabilityHash(compiledHtml) {
93314
- return createHash6("sha256").update(compiledHtml, "utf8").digest("hex").slice(0, 16);
93416
+ return createHash7("sha256").update(compiledHtml, "utf8").digest("hex").slice(0, 16);
93315
93417
  }
93316
93418
  function sanitizeObservationData(data2) {
93317
93419
  if (!data2) return void 0;
@@ -93668,7 +93770,7 @@ init_audioGain();
93668
93770
  init_compiler();
93669
93771
  init_subCompositionValidity();
93670
93772
  init_assetResolution();
93671
- import { createHash as createHash10 } from "crypto";
93773
+ import { createHash as createHash11 } from "crypto";
93672
93774
  import { createReadStream as createReadStream4, existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync14 } from "fs";
93673
93775
  import { join as join35, dirname as dirname16, resolve as resolve12, basename as basename6, relative as relative4 } from "path";
93674
93776
 
@@ -93772,7 +93874,7 @@ init_src();
93772
93874
  init_src();
93773
93875
 
93774
93876
  // ../producer/src/services/deterministicFonts.ts
93775
- import { createHash as createHash8 } from "crypto";
93877
+ import { createHash as createHash9 } from "crypto";
93776
93878
  import { existsSync as existsSync24, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
93777
93879
  import { homedir as homedir5, tmpdir as tmpdir6 } from "os";
93778
93880
  import { join as join33 } from "path";
@@ -94610,7 +94712,7 @@ function fontCacheDir(slug) {
94610
94712
  return dir;
94611
94713
  }
94612
94714
  function subsetToken(woff2Url) {
94613
- return createHash8("sha1").update(woff2Url).digest("hex").slice(0, 12);
94715
+ return createHash9("sha1").update(woff2Url).digest("hex").slice(0, 12);
94614
94716
  }
94615
94717
  function cachedWoff2Path(slug, weight, style, subset) {
94616
94718
  return join33(fontCacheDir(slug), `${weight}-${style}-${subset}.woff2`);
@@ -95041,7 +95143,7 @@ async function injectDeterministicFontFaces(html, options = {}) {
95041
95143
  init_esm10();
95042
95144
  init_dist2();
95043
95145
  init_src();
95044
- import { createHash as createHash9 } from "crypto";
95146
+ import { createHash as createHash10 } from "crypto";
95045
95147
  import {
95046
95148
  copyFileSync as copyFileSync4,
95047
95149
  existsSync as existsSync25,
@@ -95104,7 +95206,7 @@ function isUsableFile(path) {
95104
95206
  }
95105
95207
  }
95106
95208
  function computePreparedGifHash(bytes, loopIterations, padSeconds) {
95107
- return createHash9("sha256").update(CACHE_SCHEMA).update("\0").update(String(loopIterations)).update("\0").update(String(padSeconds)).update("\0").update(bytes).digest("hex");
95209
+ return createHash10("sha256").update(CACHE_SCHEMA).update("\0").update(String(loopIterations)).update("\0").update(String(padSeconds)).update("\0").update(bytes).digest("hex");
95108
95210
  }
95109
95211
  function resolveLoop(metadata, override) {
95110
95212
  if (override != null) return override;
@@ -96445,7 +96547,7 @@ function matchesScriptIntegrity(bytes, metadata) {
96445
96547
  (alg) => hashes.some((hash2) => hash2[1]?.toLowerCase() === alg)
96446
96548
  );
96447
96549
  if (!strongest) return true;
96448
- const actual = createHash10(strongest).update(bytes).digest();
96550
+ const actual = createHash11(strongest).update(bytes).digest();
96449
96551
  return hashes.some(
96450
96552
  (hash2) => hash2[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash2[2], "base64"))
96451
96553
  );
@@ -97521,11 +97623,11 @@ function resolveCaptureImageFormat(input) {
97521
97623
  }
97522
97624
 
97523
97625
  // ../producer/src/services/render/stages/probeStage.ts
97524
- var FRAME_BOUNDARY_EPSILON = 1e-3;
97626
+ var FRAME_BOUNDARY_EPSILON2 = 1e-3;
97525
97627
  function durationToFrameCount(duration, fps) {
97526
97628
  const rawFrameCount = duration * fps;
97527
97629
  const nearestFrame = Math.round(rawFrameCount);
97528
- return Math.abs(rawFrameCount - nearestFrame) <= FRAME_BOUNDARY_EPSILON ? nearestFrame : Math.ceil(rawFrameCount);
97630
+ return Math.abs(rawFrameCount - nearestFrame) <= FRAME_BOUNDARY_EPSILON2 ? nearestFrame : Math.ceil(rawFrameCount);
97529
97631
  }
97530
97632
  function hasScriptedAudioVolumeAutomation(html, audioCount) {
97531
97633
  if (audioCount <= 0) return false;
@@ -98405,7 +98507,11 @@ async function runExtractVideosStage(input) {
98405
98507
  materializeSymlinks
98406
98508
  });
98407
98509
  if (extractionResult.extracted.length > 0) {
98408
- frameLookup = createFrameLookupTable(composition.videos, extractionResult.extracted);
98510
+ frameLookup = createFrameLookupTable(
98511
+ composition.videos,
98512
+ extractionResult.extracted,
98513
+ resolveRenderFpsConfig(job.config.fps).value
98514
+ );
98409
98515
  }
98410
98516
  videoReadinessSkipIds = collectVideoReadinessSkipIds(
98411
98517
  nativeHdrVideoIds,
@@ -104369,7 +104475,7 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
104369
104475
  });
104370
104476
 
104371
104477
  // ../producer/src/services/render/stages/planHash.ts
104372
- import { createHash as createHash11 } from "crypto";
104478
+ import { createHash as createHash12 } from "crypto";
104373
104479
  var FIELD_DELIMITER = Buffer.from([0]);
104374
104480
 
104375
104481
  // ../producer/src/services/distributed/shared.ts
@@ -104383,7 +104489,7 @@ var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME;
104383
104489
  var execFile4 = promisify5(execFileCallback);
104384
104490
 
104385
104491
  // ../producer/src/services/distributed/planSize.ts
104386
- import { createHash as createHash12 } from "crypto";
104492
+ import { createHash as createHash13 } from "crypto";
104387
104493
  import { lstatSync as lstatSync5, readdirSync as readdirSync14 } from "fs";
104388
104494
  import { extname as extname7, join as join54, relative as relative7 } from "path";
104389
104495
  var PLAN_ROOT_CATEGORY = {
@@ -104420,7 +104526,7 @@ import {
104420
104526
  statSync as statSync16,
104421
104527
  writeFileSync as writeFileSync16
104422
104528
  } from "fs";
104423
- import { createHash as createHash13 } from "crypto";
104529
+ import { createHash as createHash14 } from "crypto";
104424
104530
  import { tmpdir as tmpdir9 } from "os";
104425
104531
  import { dirname as dirname25, isAbsolute as isAbsolute6, join as join58, relative as relative9, resolve as resolve17, sep as sep6 } from "path";
104426
104532
 
@@ -104471,7 +104577,7 @@ init_src();
104471
104577
 
104472
104578
  // ../producer/src/services/distributed/projectHash.ts
104473
104579
  import { readdirSync as readdirSync18, readFileSync as readFileSync22 } from "fs";
104474
- import { createHash as createHash14 } from "crypto";
104580
+ import { createHash as createHash15 } from "crypto";
104475
104581
  import { join as join62, relative as relative10 } from "path";
104476
104582
 
104477
104583
  // src/fontLocalize.ts
@@ -104513,8 +104619,8 @@ async function runFontLocalize(io, localize) {
104513
104619
  }
104514
104620
 
104515
104621
  // src/version.ts
104516
- var VERSION = true ? "0.8.78" : "0.0.0-dev";
104517
- var PRODUCER_VERSION = true ? "0.8.78" : "0.0.0-dev";
104622
+ var VERSION = true ? "0.8.80" : "0.0.0-dev";
104623
+ var PRODUCER_VERSION = true ? "0.8.80" : "0.0.0-dev";
104518
104624
 
104519
104625
  // src/fontLocalizeCli.ts
104520
104626
  async function readStdin() {