hyperframes 0.8.61 → 0.8.63

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.
@@ -55119,17 +55119,22 @@ var BrowserLeasePool;
55119
55119
  var init_browserLeasePool = __esm({
55120
55120
  "../engine/src/services/browserLeasePool.ts"() {
55121
55121
  "use strict";
55122
- BrowserLeasePool = class {
55122
+ BrowserLeasePool = class _BrowserLeasePool {
55123
55123
  constructor(options) {
55124
55124
  this.options = options;
55125
55125
  }
55126
55126
  options;
55127
+ /** Under the CLI's 3s exit watchdog, alongside the render-wait bound it races. */
55128
+ static DRAIN_FORCE_CLOSE_MS = 1e3;
55127
55129
  available = /* @__PURE__ */ new Map();
55128
55130
  entries = /* @__PURE__ */ new Set();
55129
55131
  leasesByBrowser = /* @__PURE__ */ new Map();
55130
55132
  drainPromise = null;
55133
+ closed = false;
55131
55134
  async acquire(fingerprintInput, pooled) {
55135
+ if (this.closed) throw new Error("Browser pool is closed; no new browsers can be acquired");
55132
55136
  if (this.drainPromise) await this.drainPromise;
55137
+ if (this.closed) throw new Error("Browser pool is closed; no new browsers can be acquired");
55133
55138
  const fingerprint = freezeFingerprint(fingerprintInput);
55134
55139
  const entry = this.reserveEntry(fingerprint, pooled);
55135
55140
  return this.issueLease(entry);
@@ -55188,17 +55193,30 @@ var init_browserLeasePool = __esm({
55188
55193
  if (this.drainPromise) return this.drainPromise;
55189
55194
  const entries2 = [...this.entries];
55190
55195
  for (const entry of entries2) this.requestClose(entry, false);
55191
- this.drainPromise = Promise.all(entries2.map((entry) => entry.closePromise)).then(() => {
55196
+ const settle = Promise.all(entries2.map((entry) => entry.closePromise));
55197
+ const bound = new Promise(
55198
+ (resolve18) => setTimeout(resolve18, _BrowserLeasePool.DRAIN_FORCE_CLOSE_MS).unref()
55199
+ );
55200
+ this.drainPromise = Promise.race([settle, bound]).then(() => {
55201
+ for (const entry of entries2) if (this.entries.has(entry)) this.requestClose(entry, true);
55202
+ return settle;
55203
+ }).then(() => {
55192
55204
  this.drainPromise = null;
55193
55205
  });
55194
55206
  return this.drainPromise;
55195
55207
  }
55208
+ /** Terminal: drains every entry and refuses every `acquire()` after. */
55209
+ close() {
55210
+ this.closed = true;
55211
+ return this.drain();
55212
+ }
55196
55213
  /** Test-only state reset. Call `drain()` first when entries own real browsers. */
55197
55214
  reset() {
55198
55215
  this.available.clear();
55199
55216
  this.entries.clear();
55200
55217
  this.leasesByBrowser.clear();
55201
55218
  this.drainPromise = null;
55219
+ this.closed = false;
55202
55220
  }
55203
55221
  createEntry(key2, fingerprint, pooled) {
55204
55222
  const entry = {
@@ -55732,6 +55750,9 @@ function forceReleaseBrowser(browser) {
55732
55750
  async function drainBrowserPool() {
55733
55751
  await browserLeasePool.drain();
55734
55752
  }
55753
+ async function closeBrowserPool() {
55754
+ await browserLeasePool.close();
55755
+ }
55735
55756
  function probeNvidiaVramMb() {
55736
55757
  if (_cachedVramMb !== null) return _cachedVramMb;
55737
55758
  try {
@@ -62755,11 +62776,19 @@ async function extractMediaMetadata(filePath) {
62755
62776
  });
62756
62777
  return probePromise;
62757
62778
  }
62779
+ function resolveStreamWindow(filePath, metadata) {
62780
+ const videoStreamDurationSeconds = metadata.videoStreamDurationSeconds;
62781
+ const candidateStart = metadata.videoStreamStartSeconds ?? 0;
62782
+ const videoStreamStartSeconds = Number.isFinite(candidateStart) ? candidateStart : 0;
62783
+ const cacheKey = `${filePath}\0${String(videoStreamStartSeconds)}\0${String(videoStreamDurationSeconds)}`;
62784
+ return { videoStreamStartSeconds, videoStreamDurationSeconds, cacheKey };
62785
+ }
62758
62786
  async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
62759
- const videoDurationSeconds = metadata.videoStreamDurationSeconds;
62760
- const candidateStreamStart = metadata.videoStreamStartSeconds ?? 0;
62761
- const videoStreamStartSeconds = Number.isFinite(candidateStreamStart) ? candidateStreamStart : 0;
62762
- const cacheKey = `${filePath}\0${String(videoStreamStartSeconds)}\0${String(videoDurationSeconds)}`;
62787
+ const {
62788
+ videoStreamStartSeconds,
62789
+ videoStreamDurationSeconds: videoDurationSeconds,
62790
+ cacheKey
62791
+ } = resolveStreamWindow(filePath, metadata);
62763
62792
  let probeCache = finalVideoFrameTimestampCache;
62764
62793
  if (signal) {
62765
62794
  probeCache = finalVideoFrameTimestampSignalCaches.get(signal) ?? /* @__PURE__ */ new Map();
@@ -62862,19 +62891,27 @@ async function extractAudioMetadata(filePath, options) {
62862
62891
  });
62863
62892
  return probePromise;
62864
62893
  }
62865
- async function analyzeKeyframeIntervals(filePath) {
62866
- const cached2 = keyframeCache.get(filePath);
62894
+ async function analyzeKeyframeIntervals(filePath, metadata) {
62895
+ const { videoStreamStartSeconds, videoStreamDurationSeconds, cacheKey } = resolveStreamWindow(
62896
+ filePath,
62897
+ metadata
62898
+ );
62899
+ const cached2 = keyframeCache.get(cacheKey);
62867
62900
  if (cached2) return cached2;
62868
- const promise = analyzeKeyframeIntervalsUncached(filePath);
62869
- keyframeCache.set(filePath, promise);
62901
+ const promise = analyzeKeyframeIntervalsUncached(
62902
+ filePath,
62903
+ videoStreamStartSeconds,
62904
+ videoStreamDurationSeconds
62905
+ );
62906
+ keyframeCache.set(cacheKey, promise);
62870
62907
  promise.catch(() => {
62871
- if (keyframeCache.get(filePath) === promise) {
62872
- keyframeCache.delete(filePath);
62908
+ if (keyframeCache.get(cacheKey) === promise) {
62909
+ keyframeCache.delete(cacheKey);
62873
62910
  }
62874
62911
  });
62875
62912
  return promise;
62876
62913
  }
62877
- async function analyzeKeyframeIntervalsUncached(filePath) {
62914
+ async function analyzeKeyframeIntervalsUncached(filePath, videoStreamStartSeconds, videoStreamDurationSeconds) {
62878
62915
  const stdout = await runFfprobe(filePath, [
62879
62916
  "-select_streams",
62880
62917
  "v:0",
@@ -62886,14 +62923,26 @@ async function analyzeKeyframeIntervalsUncached(filePath) {
62886
62923
  "csv=p=0"
62887
62924
  ]);
62888
62925
  const timestamps = stdout.split("\n").map((line) => parseFloat(line.trim())).filter((t) => Number.isFinite(t));
62889
- if (timestamps.length < 2) {
62926
+ if (timestamps.length === 0) {
62890
62927
  return {
62891
62928
  avgIntervalSeconds: 0,
62892
62929
  maxIntervalSeconds: 0,
62893
- keyframeCount: timestamps.length,
62930
+ keyframeCount: 0,
62894
62931
  isProblematic: false
62895
62932
  };
62896
62933
  }
62934
+ if (timestamps.length === 1) {
62935
+ const streamEnd = videoStreamStartSeconds + videoStreamDurationSeconds;
62936
+ const rawInterval = streamEnd - (timestamps[0] ?? videoStreamStartSeconds);
62937
+ const singleGopInterval = Number.isFinite(rawInterval) ? Math.max(rawInterval, 0) : 0;
62938
+ const roundedInterval = Math.round(singleGopInterval * 100) / 100;
62939
+ return {
62940
+ avgIntervalSeconds: roundedInterval,
62941
+ maxIntervalSeconds: roundedInterval,
62942
+ keyframeCount: 1,
62943
+ isProblematic: singleGopInterval > PROBLEMATIC_KEYFRAME_INTERVAL_SECONDS
62944
+ };
62945
+ }
62897
62946
  let maxInterval = 0;
62898
62947
  let totalInterval = 0;
62899
62948
  for (let i = 1; i < timestamps.length; i++) {
@@ -62906,10 +62955,10 @@ async function analyzeKeyframeIntervalsUncached(filePath) {
62906
62955
  avgIntervalSeconds: Math.round(avgInterval * 100) / 100,
62907
62956
  maxIntervalSeconds: Math.round(maxInterval * 100) / 100,
62908
62957
  keyframeCount: timestamps.length,
62909
- isProblematic: maxInterval > 2
62958
+ isProblematic: maxInterval > PROBLEMATIC_KEYFRAME_INTERVAL_SECONDS
62910
62959
  };
62911
62960
  }
62912
- var FFPROBE_STDERR_MAX_BYTES, FFPROBE_STDOUT_MAX_CHARS, FFPROBE_ERROR_MAX_CHARS, videoMetadataCache, finalVideoFrameTimestampCache, finalVideoFrameTimestampSignalCaches, audioMetadataCache, mediaProbeOutputCache, mediaProbeOutputSignalCaches, MEDIA_PROBE_OUTPUT_CACHE_MAX_ENTRIES, AAC_LC_SAMPLES_PER_PACKET, STILL_IMAGE_DEMUXERS, StructurallyIncompletePngError, extractVideoMetadata, keyframeCache;
62961
+ var FFPROBE_STDERR_MAX_BYTES, FFPROBE_STDOUT_MAX_CHARS, FFPROBE_ERROR_MAX_CHARS, videoMetadataCache, finalVideoFrameTimestampCache, finalVideoFrameTimestampSignalCaches, audioMetadataCache, mediaProbeOutputCache, mediaProbeOutputSignalCaches, MEDIA_PROBE_OUTPUT_CACHE_MAX_ENTRIES, AAC_LC_SAMPLES_PER_PACKET, STILL_IMAGE_DEMUXERS, StructurallyIncompletePngError, extractVideoMetadata, keyframeCache, PROBLEMATIC_KEYFRAME_INTERVAL_SECONDS;
62913
62962
  var init_ffprobe = __esm({
62914
62963
  "../engine/src/utils/ffprobe.ts"() {
62915
62964
  "use strict";
@@ -62952,6 +63001,7 @@ var init_ffprobe = __esm({
62952
63001
  };
62953
63002
  extractVideoMetadata = extractMediaMetadata;
62954
63003
  keyframeCache = /* @__PURE__ */ new Map();
63004
+ PROBLEMATIC_KEYFRAME_INTERVAL_SECONDS = 2;
62955
63005
  }
62956
63006
  });
62957
63007
 
@@ -64117,6 +64167,11 @@ function isUnresolvedAssetPlaceholder(rawSrc) {
64117
64167
  function cleanAssetUrl(url) {
64118
64168
  return url.trim().split(/[?#]/, 1)[0] ?? "";
64119
64169
  }
64170
+ function isWithinProjectRoot(projectDir, candidate, pathModule = { resolve: resolve5, relative, isAbsolute }) {
64171
+ const projectRoot = pathModule.resolve(projectDir);
64172
+ const relativePath = pathModule.relative(projectRoot, candidate);
64173
+ return relativePath === "" || !relativePath.startsWith("..") && !pathModule.isAbsolute(relativePath);
64174
+ }
64120
64175
  function reanchoredCandidates(variant, baseDir, compiledDir) {
64121
64176
  const baseAbs = resolve5(baseDir);
64122
64177
  const joinedAbs = resolve5(join8(baseDir, variant));
@@ -82481,6 +82536,14 @@ function readRegularFile(filePath) {
82481
82536
  closeSync4(fd);
82482
82537
  }
82483
82538
  }
82539
+ function resolveContainedPaths(projectDir, compiledDir, relativePath) {
82540
+ const projectPath = join18(projectDir, relativePath);
82541
+ if (!isWithinProjectRoot(projectDir, projectPath)) return null;
82542
+ if (!compiledDir) return { projectPath, compiledPath: null };
82543
+ const compiledPath = join18(compiledDir, relativePath);
82544
+ if (!isWithinProjectRoot(compiledDir, compiledPath)) return null;
82545
+ return { projectPath, compiledPath };
82546
+ }
82484
82547
  function createFileServer(options) {
82485
82548
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
82486
82549
  const headScripts = options.headScripts ?? [];
@@ -82490,8 +82553,9 @@ function createFileServer(options) {
82490
82553
  let requestPath = c.req.path;
82491
82554
  if (requestPath === "/") requestPath = "/index.html";
82492
82555
  const relativePath = requestPath.replace(/^\//, "");
82493
- const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
82494
- const content = (compiledPath ? readRegularFile(compiledPath) : null) ?? readRegularFile(join18(projectDir, relativePath));
82556
+ const resolved = resolveContainedPaths(projectDir, compiledDir, relativePath);
82557
+ if (!resolved) return c.text("Not found", 404);
82558
+ const content = (resolved.compiledPath ? readRegularFile(resolved.compiledPath) : null) ?? readRegularFile(resolved.projectPath);
82495
82559
  if (content === null) return c.text("Not found", 404);
82496
82560
  const ext = extname3(relativePath).toLowerCase();
82497
82561
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -82522,6 +82586,7 @@ var init_fileServer = __esm({
82522
82586
  "../engine/src/services/fileServer.ts"() {
82523
82587
  "use strict";
82524
82588
  init_compiler();
82589
+ init_assetResolution();
82525
82590
  MIME_TYPES = {
82526
82591
  ".html": "text/html; charset=utf-8",
82527
82592
  ".css": "text/css; charset=utf-8",
@@ -83675,6 +83740,7 @@ __export(src_exports, {
83675
83740
  classifyVideoExtractionError: () => classifyVideoExtractionError,
83676
83741
  cloneCaptureWarning: () => cloneCaptureWarning,
83677
83742
  cloneCaptureWarnings: () => cloneCaptureWarnings,
83743
+ closeBrowserPool: () => closeBrowserPool,
83678
83744
  closeCaptureSession: () => closeCaptureSession,
83679
83745
  completeDeferredDrawElementInit: () => completeDeferredDrawElementInit,
83680
83746
  compositionRequiresWebGpu: () => compositionRequiresWebGpu,
@@ -91976,12 +92042,14 @@ init_dist2();
91976
92042
  init_mediaTiming();
91977
92043
  init_src();
91978
92044
  var COMPOSITION_HOST_ATTR = "data-composition-file";
91979
- var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
91980
- function parseNumeric3(value) {
91981
- if (value == null || value === "") return null;
91982
- const parsed = Number.parseFloat(value);
91983
- return Number.isFinite(parsed) ? parsed : null;
92045
+ function resolveHostEnd(host, hostStart) {
92046
+ return resolveAuthoredTimingWindow({
92047
+ start: hostStart,
92048
+ duration: host.getAttribute("data-duration"),
92049
+ end: host.getAttribute("data-end")
92050
+ })?.end ?? null;
91984
92051
  }
92052
+ var ROOT_WINDOW = { offset: 0, limit: Infinity, basis: "local" };
91985
92053
  function resolveHostWindow(element, document2, startCache, visiting) {
91986
92054
  const hosts = [];
91987
92055
  for (let ancestor2 = element.parentElement; ancestor2; ancestor2 = ancestor2.parentElement) {
@@ -91992,7 +92060,7 @@ function resolveHostWindow(element, document2, startCache, visiting) {
91992
92060
  let limit = Infinity;
91993
92061
  for (const host of hosts.reverse()) {
91994
92062
  const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
91995
- const hostEnd = parseNumeric3(host.getAttribute("data-end"));
92063
+ const hostEnd = resolveHostEnd(host, hostStart);
91996
92064
  if (hostEnd != null) limit = Math.min(limit, offset2 + hostEnd);
91997
92065
  offset2 += hostStart;
91998
92066
  }
@@ -95262,10 +95330,11 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
95262
95330
  if (isHttpUrl(video.src)) continue;
95263
95331
  const videoPath = resolve12(projectDir, video.src);
95264
95332
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
95265
- Promise.all([
95266
- withMediaProbeSlot(() => analyzeKeyframeIntervals(videoPath)),
95267
- withMediaProbeSlot(() => extractMediaMetadata(videoPath))
95268
- ]).then(([analysis, metadata]) => {
95333
+ (async () => {
95334
+ const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
95335
+ const analysis = await withMediaProbeSlot(
95336
+ () => analyzeKeyframeIntervals(videoPath, metadata)
95337
+ );
95269
95338
  if (analysis.isProblematic) {
95270
95339
  defaultLogger.warn(
95271
95340
  `[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). This causes seek failures and frame freezing. Re-encode with: ${reencode}`
@@ -95276,7 +95345,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
95276
95345
  `[Compiler] Video "${video.id}" is variable frame rate (VFR); the engine will normalize it to CFR before frame extraction. If rendering feels slow on this video, pre-encode once with: ${reencode}`
95277
95346
  );
95278
95347
  }
95279
- }).catch(() => {
95348
+ })().catch(() => {
95280
95349
  });
95281
95350
  }
95282
95351
  const { document: document2 } = parseHTML(html);
@@ -102748,8 +102817,8 @@ async function runFontLocalize(io, localize) {
102748
102817
  }
102749
102818
 
102750
102819
  // src/version.ts
102751
- var VERSION = true ? "0.8.61" : "0.0.0-dev";
102752
- var PRODUCER_VERSION = true ? "0.8.61" : "0.0.0-dev";
102820
+ var VERSION = true ? "0.8.63" : "0.0.0-dev";
102821
+ var PRODUCER_VERSION = true ? "0.8.63" : "0.0.0-dev";
102753
102822
 
102754
102823
  // src/fontLocalizeCli.ts
102755
102824
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var Ee=Object.defineProperty;var pn=Object.getOwnPropertyDescriptor;var fn=Object.getOwnPropertyNames;var hn=Object.prototype.hasOwnProperty;var bn=(n,e)=>{for(var t in e)Ee(n,t,{get:e[t],enumerable:!0})},gn=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of fn(e))!hn.call(n,r)&&r!==t&&Ee(n,r,{get:()=>e[r],enumerable:!(i=pn(e,r))||i.enumerable});return n};var _n=n=>gn(Ee({},"__esModule",{value:!0}),n);var Yi={};bn(Yi,{HyperframesPlayer:()=>Ae,SPEED_PRESETS:()=>xe,formatSpeed:()=>G,formatTime:()=>ie});function dt(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function H(n){return typeof n=="object"&&n!==null}function ct(n){return H(n)&&typeof n.getDuration=="function"}function mt(n){return H(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}var L="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.61/dist/hyperframe.runtime.iife.js";function V(n){if(n===null)return null;let e=Number.parseInt(n,10);return Number.isFinite(e)&&e>0?e:null}function yn(n){let e=n?.querySelector("[data-composition-id][data-width][data-height]")??n?.querySelector("[data-width][data-height]");if(!e)return null;let t=V(e.getAttribute("data-width")),i=V(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var ne=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),r=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(dt({hasRuntime:i,hasTimelines:r,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let a=this._resolvePlaybackDurationAdapter(t);if(a&&a.getDuration()>0){this.stop();let s=yn(this._iframe.contentDocument);this._callbacks.onReady({duration:a.getDuration(),adapter:a,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||H(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=L,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!H(t))return null;let i=Object.keys(t);if(i.length===0)return null;let r=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=r&&r in t?r:i[i.length-1],a=t[o];return mt(a)?a:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(ct(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var pt=`
1
+ "use strict";var HyperframesPlayer=(()=>{var Ee=Object.defineProperty;var pn=Object.getOwnPropertyDescriptor;var fn=Object.getOwnPropertyNames;var hn=Object.prototype.hasOwnProperty;var bn=(n,e)=>{for(var t in e)Ee(n,t,{get:e[t],enumerable:!0})},gn=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of fn(e))!hn.call(n,r)&&r!==t&&Ee(n,r,{get:()=>e[r],enumerable:!(i=pn(e,r))||i.enumerable});return n};var _n=n=>gn(Ee({},"__esModule",{value:!0}),n);var Yi={};bn(Yi,{HyperframesPlayer:()=>Ae,SPEED_PRESETS:()=>xe,formatSpeed:()=>G,formatTime:()=>ie});function dt(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function H(n){return typeof n=="object"&&n!==null}function ct(n){return H(n)&&typeof n.getDuration=="function"}function mt(n){return H(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}var L="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.63/dist/hyperframe.runtime.iife.js";function V(n){if(n===null)return null;let e=Number.parseInt(n,10);return Number.isFinite(e)&&e>0?e:null}function yn(n){let e=n?.querySelector("[data-composition-id][data-width][data-height]")??n?.querySelector("[data-width][data-height]");if(!e)return null;let t=V(e.getAttribute("data-width")),i=V(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var ne=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),r=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(dt({hasRuntime:i,hasTimelines:r,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let a=this._resolvePlaybackDurationAdapter(t);if(a&&a.getDuration()>0){this.stop();let s=yn(this._iframe.contentDocument);this._callbacks.onReady({duration:a.getDuration(),adapter:a,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||H(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=L,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!H(t))return null;let i=Object.keys(t);if(i.length===0)return null;let r=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=r&&r in t?r:i[i.length-1],a=t[o];return mt(a)?a:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(ct(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var pt=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -56408,10 +56408,10 @@ function collectSubCompositionSrcs(html) {
56408
56408
  function cleanAssetUrl(url) {
56409
56409
  return url.trim().split(/[?#]/, 1)[0] ?? "";
56410
56410
  }
56411
- function isWithinProjectRoot(projectDir, candidate) {
56412
- const projectRoot = resolve(projectDir);
56413
- const relativePath = relative(projectRoot, candidate);
56414
- return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
56411
+ function isWithinProjectRoot(projectDir, candidate, pathModule = { resolve, relative, isAbsolute }) {
56412
+ const projectRoot = pathModule.resolve(projectDir);
56413
+ const relativePath = pathModule.relative(projectRoot, candidate);
56414
+ return relativePath === "" || !relativePath.startsWith("..") && !pathModule.isAbsolute(relativePath);
56415
56415
  }
56416
56416
  function addCandidate(candidates, candidate) {
56417
56417
  if (!candidates.includes(candidate)) candidates.push(candidate);
@@ -0,0 +1,21 @@
1
+ function round3(n) {
2
+ return Math.round(Number(n) * 1000) / 1000;
3
+ }
4
+
5
+ // 30ms in/out ramps kill the click at every concat boundary. A segment shorter
6
+ // than 4x the ramp would spend its whole length fading, so scale down there and
7
+ // skip entirely on a degenerate one. fadeIn/fadeOut are false at the export's
8
+ // own true start/end, where there is no splice to smooth.
9
+ export function fadeFilterFor(durationSeconds, { fadeIn, fadeOut }) {
10
+ if (!fadeIn && !fadeOut) return null;
11
+ const FADE_SECONDS = 0.03;
12
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0.01) return null;
13
+ const d = Math.min(FADE_SECONDS, durationSeconds / 4);
14
+ const parts = [];
15
+ if (fadeIn) parts.push(`afade=t=in:st=0:d=${round3(d)}`);
16
+ if (fadeOut) {
17
+ const out = round3(durationSeconds - d);
18
+ if (out > 0) parts.push(`afade=t=out:st=${out}:d=${round3(d)}`);
19
+ }
20
+ return parts.length ? parts.join(",") : null;
21
+ }
@@ -0,0 +1,33 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { fadeFilterFor } from "./transcriptCutFade.mjs";
4
+
5
+ test("fades in only at the first segment's trailing splice", () => {
6
+ assert.equal(fadeFilterFor(2, { fadeIn: false, fadeOut: true }), "afade=t=out:st=1.97:d=0.03");
7
+ });
8
+
9
+ test("fades both edges of a middle segment's two splices", () => {
10
+ assert.equal(
11
+ fadeFilterFor(2, { fadeIn: true, fadeOut: true }),
12
+ "afade=t=in:st=0:d=0.03,afade=t=out:st=1.97:d=0.03",
13
+ );
14
+ });
15
+
16
+ test("fades out only at the last segment's leading splice", () => {
17
+ assert.equal(fadeFilterFor(2, { fadeIn: true, fadeOut: false }), "afade=t=in:st=0:d=0.03");
18
+ });
19
+
20
+ test("a single kept segment borders no splice at all", () => {
21
+ assert.equal(fadeFilterFor(2, { fadeIn: false, fadeOut: false }), null);
22
+ });
23
+
24
+ test("still scales the ramp down on a short segment with both edges faded", () => {
25
+ assert.equal(
26
+ fadeFilterFor(0.08, { fadeIn: true, fadeOut: true }),
27
+ "afade=t=in:st=0:d=0.02,afade=t=out:st=0.06:d=0.02",
28
+ );
29
+ });
30
+
31
+ test("skips a degenerate segment even when a splice is requested", () => {
32
+ assert.equal(fadeFilterFor(0.005, { fadeIn: true, fadeOut: true }), null);
33
+ });
@@ -6,6 +6,7 @@ import { tmpdir } from "node:os";
6
6
  import { dirname, extname, join, resolve } from "node:path";
7
7
  import { parseArgs } from "node:util";
8
8
  import { compileCutList } from "./lib/cutlist.mjs";
9
+ import { fadeFilterFor } from "./lib/transcriptCutFade.mjs";
9
10
  import { track } from "./lib/telemetry.mjs";
10
11
 
11
12
  const { values: args } = parseArgs({
@@ -92,11 +93,23 @@ function run() {
92
93
 
93
94
  try {
94
95
  const parts = segments.map((segment, index) => {
95
- const out = join(
96
- tmpDir,
97
- `segment-${String(index).padStart(4, "0")}${extname(outPath) || ".mp4"}`,
98
- );
99
- cutSegment(inputPath, segment, out, Boolean(args.copy));
96
+ // Intermediates carry PCM audio, not the final codec. Encoding each
97
+ // segment to a lossy codec separately makes the encoder pad every segment
98
+ // with priming silence (~25-35ms for AAC), which concat then bakes in as a
99
+ // gap at each cut -- a defect distinct from, and surviving, the fades
100
+ // below. PCM has no priming, so audio is encoded exactly once, at concat.
101
+ const ext = args.copy ? extname(outPath) || ".mp4" : isAudioOnly(outPath) ? ".wav" : ".mkv";
102
+ const out = join(tmpDir, `segment-${String(index).padStart(4, "0")}${ext}`);
103
+ // --copy stays fade-free (stream copy cannot filter). A segment's true
104
+ // start/end (index 0's start, the last segment's end) borders nothing
105
+ // kept, so only an interior splice edge gets a ramp.
106
+ const fade = args.copy
107
+ ? null
108
+ : fadeFilterFor(segment.end - segment.start, {
109
+ fadeIn: index > 0,
110
+ fadeOut: index < segments.length - 1,
111
+ });
112
+ cutSegment(inputPath, segment, out, args.copy, fade);
100
113
  return out;
101
114
  });
102
115
  const listPath = join(tmpDir, "list.txt");
@@ -107,9 +120,16 @@ function run() {
107
120
  // Encode to a sibling temp (same extension so ffmpeg picks the right muxer),
108
121
  // then atomic-rename so a SIGKILL mid-encode can't leave a truncated outPath.
109
122
  const tmpOut = `${outPath}.part${extname(outPath) || ".mp4"}`;
123
+ // --copy already produced final-codec segments, so concat can stream-copy.
124
+ // Otherwise the PCM intermediates are encoded here, once, for the whole file.
125
+ const concatCodecs = args.copy
126
+ ? ["-c", "copy"]
127
+ : isAudioOnly(outPath)
128
+ ? encodeArgsFor(extname(outPath).toLowerCase())
129
+ : ["-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart"];
110
130
  execFileSync(
111
131
  "ffmpeg",
112
- ["-y", "-f", "concat", "-safe", "0", "-i", listPath, "-c", "copy", tmpOut],
132
+ ["-y", "-f", "concat", "-safe", "0", "-i", listPath, ...concatCodecs, tmpOut],
113
133
  {
114
134
  stdio: "ignore",
115
135
  },
@@ -158,7 +178,7 @@ function run() {
158
178
  console.log(`next: resolve --from ${outPath} --type <type>`);
159
179
  }
160
180
 
161
- function cutSegment(inputPath, segment, outPath, copy) {
181
+ function cutSegment(inputPath, segment, outPath, copy, fade) {
162
182
  const argv = [
163
183
  "-y",
164
184
  "-nostdin",
@@ -171,8 +191,15 @@ function cutSegment(inputPath, segment, outPath, copy) {
171
191
  ];
172
192
  if (copy) {
173
193
  argv.push("-c", "copy", "-avoid_negative_ts", "make_zero");
194
+ } else if (extname(outPath).toLowerCase() === ".mkv") {
195
+ // Concat splices raw segment edges together; without a short ramp the
196
+ // waveform steps discontinuously at every boundary and you hear a click.
197
+ if (fade) argv.push("-af", fade);
198
+ // Video intermediate: keep the picture cheap and the audio uncompressed.
199
+ argv.push("-c:v", "libx264", "-preset", "veryfast", "-crf", "18", "-c:a", "pcm_s16le");
174
200
  } else {
175
- argv.push(...encodeArgsFor(extname(outPath).toLowerCase()));
201
+ if (fade) argv.push("-af", fade);
202
+ argv.push("-c:a", "pcm_s16le");
176
203
  }
177
204
  argv.push(outPath);
178
205
  execFileSync("ffmpeg", argv, { stdio: "ignore" });
@@ -180,6 +207,10 @@ function cutSegment(inputPath, segment, outPath, copy) {
180
207
 
181
208
  // Codec set per output container. Audio-only outputs must not get the
182
209
  // video-centric aac/x264 set (aac inside .wav breaks timing entirely).
210
+ function isAudioOnly(filePath) {
211
+ return [".wav", ".mp3", ".m4a", ".aac", ".flac"].includes(extname(filePath).toLowerCase());
212
+ }
213
+
183
214
  function encodeArgsFor(ext) {
184
215
  if (ext === ".wav") return ["-c:a", "pcm_s16le"];
185
216
  if (ext === ".mp3") return ["-c:a", "libmp3lame", "-q:a", "2"];
@@ -0,0 +1,117 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync, spawnSync } from "node:child_process";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const SCRIPT = fileURLToPath(new URL("./transcript-cut.mjs", import.meta.url));
10
+ const HAS_FFMPEG =
11
+ spawnSync("ffmpeg", ["-version"], { stdio: "ignore" }).status === 0 &&
12
+ spawnSync("ffprobe", ["-version"], { stdio: "ignore" }).status === 0;
13
+
14
+ const SAMPLE_RATE = 44100;
15
+ // A continuous tone's own slope between samples never exceeds amplitude * 2*pi*f/rate
16
+ // (~2054 for 440Hz at full scale here); a raw splice between two independently-cut
17
+ // points on the same tone lands at two unrelated phases, so an unfaded join jumps far
18
+ // past that. This threshold sits well above normal tone motion and well below a splice.
19
+ const MAX_CONTINUOUS_STEP = 6000;
20
+ const MIN_SUSPICIOUS_ZERO_RUN = 20;
21
+
22
+ function fixture() {
23
+ const dir = mkdtempSync(join(tmpdir(), "media-use-transcript-cut-"));
24
+ return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
25
+ }
26
+
27
+ function run(args) {
28
+ return spawnSync(process.execPath, [SCRIPT, ...args], { encoding: "utf8" });
29
+ }
30
+
31
+ function readPcm(filePath) {
32
+ const raw = execFileSync("ffmpeg", [
33
+ "-hide_banner",
34
+ "-loglevel",
35
+ "error",
36
+ "-i",
37
+ filePath,
38
+ "-f",
39
+ "s16le",
40
+ "-ac",
41
+ "1",
42
+ "-ar",
43
+ String(SAMPLE_RATE),
44
+ "-",
45
+ ]);
46
+ const samples = new Int16Array(raw.buffer, raw.byteOffset, raw.length / 2);
47
+ return samples;
48
+ }
49
+
50
+ test(
51
+ "keeps a spliced tone continuous at every cut, with no raw phase jump or silence gap",
52
+ { skip: !HAS_FFMPEG },
53
+ (t) => {
54
+ const { dir, cleanup } = fixture();
55
+ t.after(cleanup);
56
+
57
+ const source = join(dir, "tone.wav");
58
+ execFileSync("ffmpeg", [
59
+ "-y",
60
+ "-hide_banner",
61
+ "-loglevel",
62
+ "error",
63
+ "-f",
64
+ "lavfi",
65
+ "-i",
66
+ `sine=frequency=440:duration=6:sample_rate=${SAMPLE_RATE}`,
67
+ source,
68
+ ]);
69
+
70
+ const transcriptPath = join(dir, "transcript.json");
71
+ writeFileSync(transcriptPath, JSON.stringify({ words: [{ text: "tone", start: 0, end: 6 }] }));
72
+
73
+ const output = join(dir, "out.wav");
74
+ // Two "ugly" removal ranges: neither aligned to the 440Hz period, so the kept
75
+ // segments' cut edges land at unrelated phases of the same continuous tone --
76
+ // exactly the shape that clicks without a fade, and the shape jrusso1020's
77
+ // review proved this branch never actually fades.
78
+ const result = run([
79
+ "--input",
80
+ source,
81
+ "--transcript",
82
+ transcriptPath,
83
+ "--remove",
84
+ "1.37-1.83,3.29-3.71",
85
+ "--out",
86
+ output,
87
+ "--json",
88
+ ]);
89
+ assert.equal(result.status, 0, result.stderr || result.stdout);
90
+
91
+ const samples = readPcm(output);
92
+ assert.ok(samples.length > SAMPLE_RATE, "expected several seconds of audio");
93
+
94
+ let maxStep = 0;
95
+ let zeroRun = 0;
96
+ let maxZeroRun = 0;
97
+ for (let i = 1; i < samples.length; i++) {
98
+ const step = Math.abs(samples[i] - samples[i - 1]);
99
+ if (step > maxStep) maxStep = step;
100
+ if (samples[i] === 0) {
101
+ zeroRun++;
102
+ if (zeroRun > maxZeroRun) maxZeroRun = zeroRun;
103
+ } else {
104
+ zeroRun = 0;
105
+ }
106
+ }
107
+
108
+ assert.ok(
109
+ maxStep <= MAX_CONTINUOUS_STEP,
110
+ `largest sample-to-sample step was ${maxStep}, expected <= ${MAX_CONTINUOUS_STEP} (a raw, unfaded splice)`,
111
+ );
112
+ assert.ok(
113
+ maxZeroRun < MIN_SUSPICIOUS_ZERO_RUN,
114
+ `found a run of ${maxZeroRun} consecutive zero samples, expected < ${MIN_SUSPICIOUS_ZERO_RUN} (a priming-silence gap)`,
115
+ );
116
+ },
117
+ );
@@ -1,4 +1,4 @@
1
- var we=Object.defineProperty;var Ae=(s,i,e)=>i in s?we(s,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[i]=e;var h=(s,i,e)=>Ae(s,typeof i!="symbol"?i+"":i,e);import{r as he,i as Ee,a as w,b as ue,c as Ce,d as Te,s as Re,e as Se,F as Le}from"./index-BK36b0XY.js";function xe(s){return s.hasRuntime||s.runtimeInjected?!1:!!(s.hasNestedCompositions||s.hasTimelines&&s.attempts>=5)}function $(s){return typeof s=="object"&&s!==null}function Me(s){return $(s)&&typeof s.getDuration=="function"}function De(s){return $(s)&&typeof s.duration=="function"&&typeof s.time=="function"&&typeof s.seek=="function"&&typeof s.play=="function"&&typeof s.pause=="function"}function Pe(s){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(s))throw new Error(`Invalid HyperFrames runtime version: ${s}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${s}/dist/hyperframe.runtime.iife.js`}const H=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:Pe("0.0.0-dev");function V(s){if(s===null)return null;const i=Number.parseInt(s,10);return Number.isFinite(i)&&i>0?i:null}function ke(s){const i=(s==null?void 0:s.querySelector("[data-composition-id][data-width][data-height]"))??(s==null?void 0:s.querySelector("[data-width][data-height]"));if(!i)return null;const e=V(i.getAttribute("data-width")),t=V(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class Ie{constructor(i,e){h(this,"_interval",null);h(this,"_runtimeInjected",!1);this._iframe=i,this._callbacks=e}get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let i=0;this._interval=setInterval(()=>{var e;i++;try{const t=this._iframe.contentWindow;if(!t)return;const r=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),a=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(xe({hasRuntime:r,hasTimelines:n,hasNestedCompositions:a,runtimeInjected:this._runtimeInjected,attempts:i})){this._injectRuntime();return}if(this._runtimeInjected&&!r)return;const d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();const c=ke(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:c});return}}catch{}i>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{const i=this._iframe.contentWindow;return i?this._resolveDirectTimelineAdapterFromWindow(i):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(i){return this._resolveDirectTimelineAdapterFromWindow(i)}hasRuntimeBridge(i){return Reflect.get(i,"__hf")!==void 0||$(Reflect.get(i,"__player"))}_injectRuntime(){var i,e;this._runtimeInjected=!0;try{const t=this._iframe.contentDocument;if(!t)return;const r=t.createElement("script");r.src=H,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,c;if(this.hasRuntimeBridge(i))return null;const e=Reflect.get(i,"__timelines");if(!$(e))return null;const t=Object.keys(e);if(t.length===0)return null;const r=(c=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:c.getAttribute("data-composition-id"),n=r&&r in e?r:t[t.length-1],a=e[n];return De(a)?a:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(Me(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Oe=`
1
+ var we=Object.defineProperty;var Ae=(s,i,e)=>i in s?we(s,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[i]=e;var h=(s,i,e)=>Ae(s,typeof i!="symbol"?i+"":i,e);import{r as he,i as Ee,a as w,b as ue,c as Ce,d as Te,s as Re,e as Se,F as Le}from"./index-CjueZL58.js";function xe(s){return s.hasRuntime||s.runtimeInjected?!1:!!(s.hasNestedCompositions||s.hasTimelines&&s.attempts>=5)}function $(s){return typeof s=="object"&&s!==null}function Me(s){return $(s)&&typeof s.getDuration=="function"}function De(s){return $(s)&&typeof s.duration=="function"&&typeof s.time=="function"&&typeof s.seek=="function"&&typeof s.play=="function"&&typeof s.pause=="function"}function Pe(s){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(s))throw new Error(`Invalid HyperFrames runtime version: ${s}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${s}/dist/hyperframe.runtime.iife.js`}const H=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:Pe("0.0.0-dev");function V(s){if(s===null)return null;const i=Number.parseInt(s,10);return Number.isFinite(i)&&i>0?i:null}function ke(s){const i=(s==null?void 0:s.querySelector("[data-composition-id][data-width][data-height]"))??(s==null?void 0:s.querySelector("[data-width][data-height]"));if(!i)return null;const e=V(i.getAttribute("data-width")),t=V(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class Ie{constructor(i,e){h(this,"_interval",null);h(this,"_runtimeInjected",!1);this._iframe=i,this._callbacks=e}get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let i=0;this._interval=setInterval(()=>{var e;i++;try{const t=this._iframe.contentWindow;if(!t)return;const r=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),a=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(xe({hasRuntime:r,hasTimelines:n,hasNestedCompositions:a,runtimeInjected:this._runtimeInjected,attempts:i})){this._injectRuntime();return}if(this._runtimeInjected&&!r)return;const d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();const c=ke(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:c});return}}catch{}i>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{const i=this._iframe.contentWindow;return i?this._resolveDirectTimelineAdapterFromWindow(i):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(i){return this._resolveDirectTimelineAdapterFromWindow(i)}hasRuntimeBridge(i){return Reflect.get(i,"__hf")!==void 0||$(Reflect.get(i,"__player"))}_injectRuntime(){var i,e;this._runtimeInjected=!0;try{const t=this._iframe.contentDocument;if(!t)return;const r=t.createElement("script");r.src=H,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,c;if(this.hasRuntimeBridge(i))return null;const e=Reflect.get(i,"__timelines");if(!$(e))return null;const t=Object.keys(e);if(t.length===0)return null;const r=(c=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:c.getAttribute("data-composition-id"),n=r&&r in e?r:t[t.length-1],a=e[n];return De(a)?a:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(Me(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Oe=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;