@hyperframes/producer 0.8.193 → 0.8.195

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1564,8 +1564,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
1564
1564
  `Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
1565
1565
  );
1566
1566
  }
1567
+ function hasUsableFrameCount(frames) {
1568
+ return frames !== void 0 && Number.isFinite(frames) && frames > 0;
1569
+ }
1567
1570
  function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
1568
- if (probedFrames === void 0 || !Number.isFinite(probedFrames) || probedFrames <= 0) {
1571
+ if (!hasUsableFrameCount(probedFrames)) {
1569
1572
  return;
1570
1573
  }
1571
1574
  const shortfall = expectedFrames - probedFrames;
@@ -1585,9 +1588,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
1585
1588
  );
1586
1589
  }
1587
1590
  var ArtifactTransaction = class {
1588
- constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
1591
+ constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
1589
1592
  this.kind = kind;
1590
1593
  this.fileSystem = fileSystem;
1594
+ this.logger = logger;
1591
1595
  this.destinationPath = resolve4(destinationPath);
1592
1596
  this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
1593
1597
  this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
@@ -1670,6 +1674,19 @@ var ArtifactTransaction = class {
1670
1674
  probePath
1671
1675
  );
1672
1676
  if (expected.expectedFrames !== void 0) {
1677
+ if (!hasUsableFrameCount(probed.frames)) {
1678
+ try {
1679
+ this.logger.debug(
1680
+ "[Render] Frame-count validation skipped: no usable frame count was reported.",
1681
+ {
1682
+ event: "artifact_frame_count_unavailable",
1683
+ artifact: this.destinationPath,
1684
+ expectedFrames: expected.expectedFrames
1685
+ }
1686
+ );
1687
+ } catch {
1688
+ }
1689
+ }
1673
1690
  assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
1674
1691
  }
1675
1692
  assertSizeMatches(expected, probed, probePath);
@@ -5252,12 +5269,15 @@ import {
5252
5269
  readMediaStart,
5253
5270
  redactTelemetryString as redactTelemetryString2,
5254
5271
  resolveNaturalMediaTimelineDurationFromValues,
5272
+ rewriteAssetPath,
5255
5273
  rewriteAssetPaths,
5256
5274
  rewriteCssAssetUrls,
5257
5275
  rewriteInlineStyleAssetUrls
5258
5276
  } from "@hyperframes/core";
5259
5277
  import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
5260
- import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
5278
+ import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
5279
+ import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
5280
+ import { gsapCdnDist, normalizeGsapBaseUrl } from "@hyperframes/core/gsap-cdn";
5261
5281
  import {
5262
5282
  assignBundledRuntimeCompositionIds,
5263
5283
  assignMediaRenderIds,
@@ -6290,7 +6310,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6290
6310
  const results = await Promise.all(
6291
6311
  workItems.map(async (item) => {
6292
6312
  const { html: compiledSub } = await compileHtmlFile(
6293
- item.rawSubHtml,
6313
+ rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
6294
6314
  dirname6(item.filePath),
6295
6315
  downloadDir,
6296
6316
  void 0,
@@ -6464,7 +6484,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
6464
6484
  let compHtml = subCompositions.get(srcPath) || null;
6465
6485
  if (!compHtml) {
6466
6486
  const read = readProjectFile(resolve6(projectDir, srcPath));
6467
- if (read.kind === "file") compHtml = read.text;
6487
+ if (read.kind === "file") {
6488
+ compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
6489
+ }
6468
6490
  }
6469
6491
  return compHtml;
6470
6492
  },
@@ -7007,18 +7029,27 @@ async function embedLocalFontFaces(html, projectDir) {
7007
7029
  }
7008
7030
  return result;
7009
7031
  }
7010
- var GSAP_CDN_BASE = gsapCdnDist();
7011
- function rewriteUnresolvableGsapToCdn(html, projectDir) {
7032
+ function renderGsapDist() {
7033
+ try {
7034
+ return gsapCdnDist(void 0, normalizeGsapBaseUrl(process.env.HYPERFRAMES_GSAP_BASE_URL));
7035
+ } catch (cause) {
7036
+ throw new Error(
7037
+ "Invalid HYPERFRAMES_GSAP_BASE_URL: use an absolute HTTPS npm mirror root (plain HTTP only for localhost) without credentials, query, or fragment",
7038
+ { cause }
7039
+ );
7040
+ }
7041
+ }
7042
+ function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
7043
+ let gsapDist;
7044
+ const exists = (path) => existsSync10(resolve6(projectDir, path));
7012
7045
  return html.replace(
7013
7046
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
7014
7047
  (full, prefix, src, file, suffix) => {
7015
7048
  if (/^https?:\/\//i.test(src)) return full;
7016
- const absPath = resolve6(projectDir, src);
7017
- if (existsSync10(absPath)) return full;
7018
- defaultLogger.info(
7019
- `[Compiler] Rewriting missing gsap script to CDN: ${src} \u2192 ${GSAP_CDN_BASE}${file}`
7020
- );
7021
- return `${prefix}${GSAP_CDN_BASE}${file}${suffix}`;
7049
+ if (exists(compSrcPath ? rewriteAssetPath(compSrcPath, src, exists) : src)) return full;
7050
+ gsapDist ??= renderGsapDist();
7051
+ defaultLogger.info(`[Compiler] Rewriting missing gsap script: ${src} \u2192 ${gsapDist}${file}`);
7052
+ return `${prefix}${gsapDist}${file}${suffix}`;
7022
7053
  }
7023
7054
  );
7024
7055
  }
@@ -7232,6 +7263,9 @@ async function discoverMediaFromBrowser(page) {
7232
7263
  const intrinsicDuration = isImage ? 0 : htmlEl.duration;
7233
7264
  const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
7234
7265
  const mediaStartRaw = htmlEl.getAttribute("data-media-start");
7266
+ const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
7267
+ const automationRaw = htmlEl.getAttribute("data-automation");
7268
+ const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
7235
7269
  const loop = htmlEl.hasAttribute("loop");
7236
7270
  const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
7237
7271
  const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
@@ -7247,6 +7281,9 @@ async function discoverMediaFromBrowser(page) {
7247
7281
  durationInferred,
7248
7282
  playbackStartRaw,
7249
7283
  mediaStartRaw,
7284
+ playbackRateRaw,
7285
+ automationRaw,
7286
+ defaultPlaybackRate,
7250
7287
  loop,
7251
7288
  hasAudio,
7252
7289
  volume,
@@ -7256,7 +7293,17 @@ async function discoverMediaFromBrowser(page) {
7256
7293
  return results;
7257
7294
  });
7258
7295
  return elements.map(
7259
- ({ endRaw, durationRaw, intrinsicDuration, playbackStartRaw, mediaStartRaw, ...element }) => ({
7296
+ ({
7297
+ endRaw,
7298
+ durationRaw,
7299
+ intrinsicDuration,
7300
+ playbackStartRaw,
7301
+ mediaStartRaw,
7302
+ playbackRateRaw,
7303
+ automationRaw,
7304
+ defaultPlaybackRate,
7305
+ ...element
7306
+ }) => ({
7260
7307
  ...element,
7261
7308
  end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
7262
7309
  duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
@@ -7266,7 +7313,14 @@ async function discoverMediaFromBrowser(page) {
7266
7313
  if (name === "data-media-start") return mediaStartRaw;
7267
7314
  return null;
7268
7315
  }
7269
- })
7316
+ }),
7317
+ playbackRate: resolveRateSpec(
7318
+ automationRaw,
7319
+ readPlaybackRate(
7320
+ (name) => name === "data-playback-rate" ? playbackRateRaw : null,
7321
+ defaultPlaybackRate
7322
+ )
7323
+ )
7270
7324
  })
7271
7325
  );
7272
7326
  }
@@ -7686,9 +7740,18 @@ import {
7686
7740
  getCompositionDuration,
7687
7741
  initializeSession as initializeSession4,
7688
7742
  isTransientBrowserError,
7689
- probeBeginFrameLiveness
7743
+ probeBeginFrameLiveness,
7744
+ videoAudioId
7690
7745
  } from "@hyperframes/engine";
7691
- import { MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2, fpsToNumber as fpsToNumber7, readMediaStart as readMediaStart2 } from "@hyperframes/core";
7746
+ import {
7747
+ MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
7748
+ fpsToNumber as fpsToNumber7,
7749
+ normalizeRateSpec as normalizeRateSpec3,
7750
+ readMediaStart as readMediaStart2,
7751
+ shiftRateLane as shiftRateLane2,
7752
+ sourceTimeAt as sourceTimeAt3,
7753
+ timeAtSourceTime
7754
+ } from "@hyperframes/core";
7692
7755
  import { extractMediaSrcMutations } from "@hyperframes/parsers";
7693
7756
 
7694
7757
  // src/services/render/stages/probeFailures.ts
@@ -7747,16 +7810,28 @@ function authoredMediaStarts(html) {
7747
7810
  }
7748
7811
  return starts;
7749
7812
  }
7750
- function adoptRuntimeMediaStart(composition, el, authored) {
7813
+ function isVideoAudio(audio, videoId) {
7814
+ return audio.type === "video" && audio.id === videoAudioId(videoId);
7815
+ }
7816
+ function adoptRuntimeMediaTiming(composition, el, authored) {
7751
7817
  const shift = el.mediaStart - authored;
7752
- if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
7753
7818
  const entries = el.tagName === "video" ? [
7754
7819
  ...composition.videos.filter((video) => video.id === el.id),
7755
- ...composition.audios.filter(
7756
- (audio) => audio.type === "video" && audio.id === `${el.id}-audio`
7757
- )
7820
+ ...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
7758
7821
  ] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
7759
- for (const entry of entries) entry.mediaStart += shift;
7822
+ for (const entry of entries) {
7823
+ if (typeof entry.playbackRate !== "object") {
7824
+ if (typeof el.playbackRate === "object") {
7825
+ const consumed = Math.max(0, entry.mediaStart - authored);
7826
+ const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
7827
+ entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
7828
+ entry.playbackRate = shiftRateLane2(el.playbackRate, head);
7829
+ } else {
7830
+ entry.playbackRate = el.playbackRate;
7831
+ }
7832
+ }
7833
+ if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
7834
+ }
7760
7835
  }
7761
7836
  function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
7762
7837
  if (projectedEnd <= 0) return existingEnd;
@@ -8040,7 +8115,7 @@ async function runProbeStage(input) {
8040
8115
  sourceChanged,
8041
8116
  el.durationInferred
8042
8117
  );
8043
- adoptRuntimeMediaStart(
8118
+ adoptRuntimeMediaTiming(
8044
8119
  composition,
8045
8120
  el,
8046
8121
  authoredStarts.get(el.id) ?? existing.mediaStart
@@ -8063,6 +8138,7 @@ async function runProbeStage(input) {
8063
8138
  start: el.start,
8064
8139
  end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
8065
8140
  mediaStart: el.mediaStart,
8141
+ playbackRate: el.playbackRate,
8066
8142
  loop: el.loop,
8067
8143
  hasAudio: el.hasAudio && !el.muted
8068
8144
  });
@@ -8087,7 +8163,7 @@ async function runProbeStage(input) {
8087
8163
  sourceChanged,
8088
8164
  el.durationInferred
8089
8165
  );
8090
- adoptRuntimeMediaStart(
8166
+ adoptRuntimeMediaTiming(
8091
8167
  composition,
8092
8168
  el,
8093
8169
  authoredStarts.get(el.id) ?? existing.mediaStart
@@ -8105,6 +8181,7 @@ async function runProbeStage(input) {
8105
8181
  end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
8106
8182
  mediaStart: el.mediaStart,
8107
8183
  loop: el.loop,
8184
+ playbackRate: el.playbackRate,
8108
8185
  layer: 0,
8109
8186
  volume: el.volume,
8110
8187
  type: "audio"
@@ -8365,7 +8442,8 @@ import {
8365
8442
  isHttpUrl as isHttpUrl2,
8366
8443
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
8367
8444
  runVideoExtractionWithRetry,
8368
- safeVideoExtractionSourceIdentity
8445
+ safeVideoExtractionSourceIdentity,
8446
+ videoAudioId as videoAudioId2
8369
8447
  } from "@hyperframes/engine";
8370
8448
 
8371
8449
  // src/services/render/extractionFailureMetadata.ts
@@ -8723,7 +8801,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
8723
8801
  const video = composition.videos.find((v) => v.id === ext.videoId);
8724
8802
  if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
8725
8803
  composition.audios.push({
8726
- id: `${video.id}-audio`,
8804
+ id: videoAudioId2(video.id),
8727
8805
  src: video.src,
8728
8806
  start: video.start,
8729
8807
  end: video.end,
@@ -11749,13 +11827,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
11749
11827
  return false;
11750
11828
  }
11751
11829
  }
11752
- async function resolveCompositionElementCount(probeSession, html) {
11830
+ var PROBE_READ_TIMEOUT_MS = 5e3;
11831
+ async function evaluateWithinBound(page, fn, what, log) {
11832
+ let timer;
11833
+ const timedOut = new Promise((_, reject) => {
11834
+ timer = setTimeout(
11835
+ () => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
11836
+ PROBE_READ_TIMEOUT_MS
11837
+ );
11838
+ });
11839
+ try {
11840
+ return await Promise.race([page.evaluate(fn), timedOut]);
11841
+ } catch (err) {
11842
+ log.warn(`[Render] live ${what} read skipped; using the static answer`, {
11843
+ error: err instanceof Error ? err.message : String(err)
11844
+ });
11845
+ throw err;
11846
+ } finally {
11847
+ clearTimeout(timer);
11848
+ }
11849
+ }
11850
+ async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
11753
11851
  if (probeSession?.isInitialized) {
11754
11852
  try {
11755
- const liveCount = await probeSession.page.evaluate(
11853
+ const liveCount = await evaluateWithinBound(
11854
+ probeSession.page,
11756
11855
  // Live HTMLCollection length — avoids materializing a static NodeList
11757
11856
  // on the large-DOM comps this gate exists to catch (review nit).
11758
- () => document.getElementsByTagName("*").length
11857
+ () => document.getElementsByTagName("*").length,
11858
+ "element count",
11859
+ log
11759
11860
  );
11760
11861
  if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
11761
11862
  return { count: liveCount, source: "live" };
@@ -11843,11 +11944,11 @@ function probeAdaptersUsed() {
11843
11944
  }
11844
11945
  return used;
11845
11946
  }
11846
- async function resolveAdaptersUsed(probeSession, html) {
11947
+ async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
11847
11948
  const staticAdapters = detectAdaptersStatic(html);
11848
11949
  if (!probeSession?.isInitialized) return staticAdapters;
11849
11950
  try {
11850
- const live = await probeSession.page.evaluate(probeAdaptersUsed);
11951
+ const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
11851
11952
  if (!Array.isArray(live)) return staticAdapters;
11852
11953
  const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
11853
11954
  return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
@@ -12105,7 +12206,10 @@ async function executeRenderPipeline(input) {
12105
12206
  );
12106
12207
  const artifactTransaction = new ArtifactTransaction(
12107
12208
  outputPath,
12108
- isPngSequence || isHls ? "directory" : "file"
12209
+ isPngSequence || isHls ? "directory" : "file",
12210
+ void 0,
12211
+ void 0,
12212
+ log
12109
12213
  );
12110
12214
  const stagedOutputPath = artifactTransaction.stagingPath;
12111
12215
  const needsAlpha = outputNeedsAlpha(outputFormat);
@@ -12604,8 +12708,8 @@ async function executeRenderPipeline(input) {
12604
12708
  vfxHostCount,
12605
12709
  vfxCapture,
12606
12710
  vfxTypes
12607
- } = await resolveCompositionElementCount(probeSession, compiled.html);
12608
- const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
12711
+ } = await resolveCompositionElementCount(probeSession, compiled.html, log);
12712
+ const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
12609
12713
  const deShortBandEnabled = deShortBandMaxElements > 0;
12610
12714
  const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
12611
12715
  const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";