hyperframes 0.7.8 → 0.7.9

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.7.8" : "0.0.0-dev";
53
+ VERSION = true ? "0.7.9" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -21074,6 +21074,10 @@ function resolveTimelinePositions(anims) {
21074
21074
  let cursor = 0;
21075
21075
  let prevStart = 0;
21076
21076
  for (const anim of anims) {
21077
+ if (anim.method === "set" && anim.global) {
21078
+ anim.resolvedStart = 0;
21079
+ continue;
21080
+ }
21077
21081
  const duration = anim.method === "set" ? 0 : anim.duration ?? GSAP_DEFAULT_DURATION;
21078
21082
  let start;
21079
21083
  if (anim.implicitPosition) {
@@ -25380,11 +25384,40 @@ function describeStudioElement(tag) {
25380
25384
  parts.push(">");
25381
25385
  return parts.join("");
25382
25386
  }
25383
- var coreRules;
25387
+ function findCodeFenceLeak(headWithoutValidBlocks) {
25388
+ return MARKDOWN_CODE_FENCE_PATTERN.exec(headWithoutValidBlocks)?.[0] ?? null;
25389
+ }
25390
+ function findOrphanCssLeak(headContent) {
25391
+ const residualText = headContent.replace(HEAD_BLOCKS_TO_IGNORE_PATTERN, " ").replace(HTML_TAG_PATTERN, " ");
25392
+ return ORPHAN_CSS_AT_RULE_PATTERN.exec(residualText)?.[0] ?? ORPHAN_CSS_RULE_PATTERN.exec(residualText)?.[0] ?? null;
25393
+ }
25394
+ function findStrayCloseLeak(headWithoutValidBlocks) {
25395
+ return STRAY_HEAD_CLOSE_PATTERN.exec(headWithoutValidBlocks)?.[0] ?? null;
25396
+ }
25397
+ function findLeakedTextInHeadContent(headContent) {
25398
+ const withoutValidBlocks = headContent.replace(HEAD_BLOCKS_TO_IGNORE_PATTERN, " ");
25399
+ return findCodeFenceLeak(withoutValidBlocks) ?? findOrphanCssLeak(headContent) ?? findStrayCloseLeak(withoutValidBlocks);
25400
+ }
25401
+ function findLeakedTextInHead(rawSource) {
25402
+ const headMatches = [...rawSource.matchAll(HEAD_CONTENT_PATTERN)];
25403
+ for (const match of headMatches) {
25404
+ const leakedText = findLeakedTextInHeadContent(match[1] ?? "");
25405
+ if (leakedText) return leakedText;
25406
+ }
25407
+ return null;
25408
+ }
25409
+ var HEAD_BLOCKS_TO_IGNORE_PATTERN, HTML_TAG_PATTERN, HEAD_CONTENT_PATTERN, STRAY_HEAD_CLOSE_PATTERN, MARKDOWN_CODE_FENCE_PATTERN, ORPHAN_CSS_AT_RULE_PATTERN, ORPHAN_CSS_RULE_PATTERN, coreRules;
25384
25410
  var init_core = __esm({
25385
25411
  "../core/src/lint/rules/core.ts"() {
25386
25412
  "use strict";
25387
25413
  init_utils2();
25414
+ HEAD_BLOCKS_TO_IGNORE_PATTERN = /<(?:style|script|template|title|noscript)\b[^>]*>[\s\S]*?<\/(?:style|script|template|title|noscript)(?:\s[^>]*)?>/gi;
25415
+ HTML_TAG_PATTERN = /<[^>]+>/g;
25416
+ HEAD_CONTENT_PATTERN = /<head\b[^>]*>([\s\S]*?)(?:<\/head>|<body\b|$)/gi;
25417
+ STRAY_HEAD_CLOSE_PATTERN = /<\/(?:style|script)(?:\s[^>]*)?>/i;
25418
+ MARKDOWN_CODE_FENCE_PATTERN = /```[^\r\n`]*(?:\r?\n|$)[\s\S]*?```/i;
25419
+ ORPHAN_CSS_AT_RULE_PATTERN = /(?:^|\s)@(?:container|font-face|keyframes|layer|media|page|property|scope|supports)[^{<]*\{[\s\S]*?:[\s\S]*?\}/i;
25420
+ ORPHAN_CSS_RULE_PATTERN = /(?:^|\s)(?:\/\*[\s\S]*?\*\/\s*)?(?:@[a-z-]+[^{}<]*|[.#][\w-]+[^{}<]*|[a-z][\w-]*(?:\s+[.#:[\w-][^{}<]*)?)\s*\{[^{}]*:[^{}]*\}/i;
25388
25421
  coreRules = [
25389
25422
  // root_missing_composition_id + root_missing_dimensions
25390
25423
  ({ rootTag }) => {
@@ -25411,6 +25444,20 @@ var init_core = __esm({
25411
25444
  }
25412
25445
  return findings;
25413
25446
  },
25447
+ // head_leaked_text
25448
+ ({ source }) => {
25449
+ const snippet = findLeakedTextInHead(source);
25450
+ if (!snippet) return [];
25451
+ return [
25452
+ {
25453
+ code: "head_leaked_text",
25454
+ severity: "error",
25455
+ message: "Detected leaked code or CSS text in the document `<head>`. Browsers render this as visible text in the video.",
25456
+ fixHint: "Move CSS into a single `<style>...</style>` block and remove stray close tags, markdown fences, or code text from `<head>`.",
25457
+ snippet: truncateSnippet(snippet)
25458
+ }
25459
+ ];
25460
+ },
25414
25461
  // missing_timeline_registry + timeline_registry_missing_init
25415
25462
  ({ source, rawSource, options }) => {
25416
25463
  if (options.isSubComposition || rawSource.trimStart().toLowerCase().startsWith("<template")) {
@@ -32424,6 +32471,7 @@ __export(src_exports, {
32424
32471
  HYPERFRAME_RUNTIME_GLOBALS: () => HYPERFRAME_RUNTIME_GLOBALS,
32425
32472
  ITEM_TYPES: () => ITEM_TYPES,
32426
32473
  ITEM_TYPE_DIRS: () => ITEM_TYPE_DIRS,
32474
+ MEDIA_DURATION_CLAMP_EPSILON_SECONDS: () => MEDIA_DURATION_CLAMP_EPSILON_SECONDS,
32427
32475
  MEDIA_STYLES: () => MEDIA_STYLES,
32428
32476
  MEDIA_VISUAL_STYLE_PROPERTIES: () => MEDIA_VISUAL_STYLE_PROPERTIES,
32429
32477
  TEXT_STYLES: () => TEXT_STYLES,
@@ -39113,6 +39161,15 @@ function getFrameAtTime(extracted, globalTime, videoStart, loop = false, mediaSt
39113
39161
  if (frameIndex < 0 || frameIndex >= extracted.totalFrames) return null;
39114
39162
  return extracted.framePaths.get(frameIndex) || null;
39115
39163
  }
39164
+ function analyzeClipMediaFit(params) {
39165
+ const { slotSeconds, mediaSeconds, loop } = params;
39166
+ if (loop) return null;
39167
+ if (!(slotSeconds > 0) || !Number.isFinite(mediaSeconds) || mediaSeconds < 0) return null;
39168
+ const toleranceSeconds = MEDIA_DURATION_CLAMP_EPSILON_SECONDS;
39169
+ const shortfallSeconds = slotSeconds - mediaSeconds;
39170
+ if (shortfallSeconds <= toleranceSeconds) return null;
39171
+ return { shortfallSeconds, toleranceSeconds };
39172
+ }
39116
39173
  function createFrameLookupTable(videos, extracted) {
39117
39174
  const table = new FrameLookupTable();
39118
39175
  const extractedMap = /* @__PURE__ */ new Map();
@@ -39123,7 +39180,7 @@ function createFrameLookupTable(videos, extracted) {
39123
39180
  }
39124
39181
  return table;
39125
39182
  }
39126
- var VIDEO_FRAME_FORMATS, ALPHA_CAPABLE_CODECS, FrameLookupTable;
39183
+ var VIDEO_FRAME_FORMATS, ALPHA_CAPABLE_CODECS, HOLD_LAST_FRAME_TOLERANCE_FRAMES, FrameLookupTable;
39127
39184
  var init_videoFrameExtractor = __esm({
39128
39185
  "../engine/src/services/videoFrameExtractor.ts"() {
39129
39186
  "use strict";
@@ -39140,6 +39197,7 @@ var init_videoFrameExtractor = __esm({
39140
39197
  init_extractionCache();
39141
39198
  VIDEO_FRAME_FORMATS = ["auto", "jpg", "png"];
39142
39199
  ALPHA_CAPABLE_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8", "prores"]);
39200
+ HOLD_LAST_FRAME_TOLERANCE_FRAMES = 2;
39143
39201
  FrameLookupTable = class {
39144
39202
  videos = /* @__PURE__ */ new Map();
39145
39203
  orderedVideos = [];
@@ -39218,7 +39276,12 @@ var init_videoFrameExtractor = __esm({
39218
39276
  continue;
39219
39277
  }
39220
39278
  if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) {
39221
- if (globalTime >= video.end && video.extracted.totalFrames > 0) {
39279
+ const fps = video.extracted.fps;
39280
+ const holdTolerance = Math.max(
39281
+ fps > 0 ? HOLD_LAST_FRAME_TOLERANCE_FRAMES / fps : 0,
39282
+ MEDIA_DURATION_CLAMP_EPSILON_SECONDS
39283
+ );
39284
+ if (globalTime >= video.end - holdTolerance && video.extracted.totalFrames > 0) {
39222
39285
  const lastIndex = video.extracted.totalFrames - 1;
39223
39286
  const lastPath = video.extracted.framePaths.get(lastIndex);
39224
39287
  if (lastPath) frames.set(videoId, { framePath: lastPath, frameIndex: lastIndex });
@@ -42099,6 +42162,7 @@ __export(src_exports2, {
42099
42162
  TRANSITIONS: () => TRANSITIONS,
42100
42163
  VIDEO_FRAME_FORMATS: () => VIDEO_FRAME_FORMATS,
42101
42164
  acquireBrowser: () => acquireBrowser,
42165
+ analyzeClipMediaFit: () => analyzeClipMediaFit,
42102
42166
  analyzeCompositionHdr: () => analyzeCompositionHdr,
42103
42167
  analyzeKeyframeIntervals: () => analyzeKeyframeIntervals,
42104
42168
  applyDomLayerMask: () => applyDomLayerMask,
@@ -84503,6 +84567,10 @@ function resolveTimelinePositions2(anims) {
84503
84567
  let cursor = 0;
84504
84568
  let prevStart = 0;
84505
84569
  for (const anim of anims) {
84570
+ if (anim.method === "set" && anim.global) {
84571
+ anim.resolvedStart = 0;
84572
+ continue;
84573
+ }
84506
84574
  const duration = anim.method === "set" ? 0 : anim.duration ?? GSAP_DEFAULT_DURATION2;
84507
84575
  let start;
84508
84576
  if (anim.implicitPosition) {
@@ -95590,7 +95658,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
95590
95658
  const duration = effectiveDuration > 0 ? effectiveDuration : fileDuration;
95591
95659
  return { duration, resolvedPath: filePath };
95592
95660
  }
95593
- async function compileHtmlFile(html, baseDir, downloadDir) {
95661
+ async function compileHtmlFile(html, baseDir, downloadDir, log2) {
95594
95662
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
95595
95663
  const mediaUnresolved = unresolved.filter(
95596
95664
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -95615,13 +95683,17 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
95615
95683
  downloadDir,
95616
95684
  el.tagName
95617
95685
  );
95618
- return { id: el.id, duration: el.duration, maxDuration, src: el.src };
95686
+ return { id: el.id, tagName: el.tagName, duration: el.duration, maxDuration, src: el.src };
95619
95687
  })
95620
95688
  );
95621
95689
  const clampList = [];
95622
95690
  for (const r2 of clampResults) {
95623
95691
  if (r2.maxDuration > 0 && shouldClampMediaDuration(r2.duration, r2.maxDuration)) {
95624
95692
  clampList.push({ id: r2.id, duration: r2.maxDuration });
95693
+ const kind = r2.tagName === "audio" ? "Audio" : "Video";
95694
+ log2?.warn(
95695
+ `[compile] ${kind} "${r2.id}" (${r2.src}) is ${r2.maxDuration.toFixed(2)}s but its data-duration is ${r2.duration.toFixed(2)}s \u2014 the slot is shortened to the media length. Set data-duration to ~${r2.maxDuration.toFixed(2)}s, trim data-media-start, or use a longer/looping source if that isn't intended.`
95696
+ );
95625
95697
  }
95626
95698
  }
95627
95699
  if (clampList.length > 0) {
@@ -96310,7 +96382,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
96310
96382
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
96311
96383
  rawHtml,
96312
96384
  projectDir,
96313
- downloadDir
96385
+ downloadDir,
96386
+ options.log
96314
96387
  );
96315
96388
  const {
96316
96389
  videos: subVideos,
@@ -96709,6 +96782,7 @@ async function runCompileStage(input2) {
96709
96782
  } = input2;
96710
96783
  const compileStart = Date.now();
96711
96784
  const compiled = await compileForRender(projectDir, htmlPath, join43(workDir, "downloads"), {
96785
+ log: log2,
96712
96786
  failClosedFontFetch: failClosedFontFetch === true,
96713
96787
  allowSystemFontCapture,
96714
96788
  animatedGifCacheDir: cfg.extractCacheDir ? join43(cfg.extractCacheDir, "animated-gif") : void 0,
@@ -113556,6 +113630,47 @@ async function seekTo2(page, time) {
113556
113630
  }, time);
113557
113631
  await new Promise((r2) => setTimeout(r2, SEEK_SETTLE_MS2));
113558
113632
  }
113633
+ async function auditClipDurations(page, analyzeClipMediaFit2) {
113634
+ const clips = await page.evaluate(() => {
113635
+ const rows = [];
113636
+ document.querySelectorAll("video[data-duration], audio[data-duration]").forEach((node) => {
113637
+ const el = node;
113638
+ const slot = parseFloat(el.getAttribute("data-duration") ?? "");
113639
+ if (!(slot > 0)) return;
113640
+ rows.push({
113641
+ id: el.id || el.getAttribute("src") || `(${el.tagName.toLowerCase()})`,
113642
+ kind: el.tagName === "AUDIO" ? "Audio" : "Video",
113643
+ slot,
113644
+ mediaStart: parseFloat(el.getAttribute("data-media-start") ?? "0") || 0,
113645
+ duration: el.duration,
113646
+ loop: el.loop || el.getAttribute("data-loop") === "true"
113647
+ });
113648
+ });
113649
+ return rows;
113650
+ });
113651
+ const warnings = [];
113652
+ const unreadable = [];
113653
+ for (const clip of clips) {
113654
+ if (!Number.isFinite(clip.duration) || clip.duration <= 0) {
113655
+ unreadable.push(clip.id);
113656
+ continue;
113657
+ }
113658
+ const mediaSeconds = Math.max(0, clip.duration - clip.mediaStart);
113659
+ const fit = analyzeClipMediaFit2({ slotSeconds: clip.slot, mediaSeconds, loop: clip.loop });
113660
+ if (!fit) continue;
113661
+ warnings.push({
113662
+ level: "warning",
113663
+ text: `${clip.kind} "${clip.id}" is ${mediaSeconds.toFixed(2)}s but its slot (data-duration) is ${clip.slot.toFixed(2)}s \u2014 the slot is shortened to the media length when rendered. Set data-duration to ~${mediaSeconds.toFixed(2)}s if that isn't intended.`
113664
+ });
113665
+ }
113666
+ if (unreadable.length > 0) {
113667
+ warnings.push({
113668
+ level: "warning",
113669
+ text: `Could not read the duration of ${unreadable.length} media element(s) within the validate timeout (${unreadable.join(", ")}); their slot vs. source fit was not checked. Re-run with a longer --timeout if the source is slow to load.`
113670
+ });
113671
+ }
113672
+ return warnings;
113673
+ }
113559
113674
  async function runContrastAudit(page) {
113560
113675
  const duration = await getCompositionDuration3(page);
113561
113676
  if (duration <= 0) return [];
@@ -113597,7 +113712,7 @@ async function validateInBrowser(projectDir, opts) {
113597
113712
  try {
113598
113713
  const browser = await ensureBrowser2();
113599
113714
  const puppeteer = await import("puppeteer-core");
113600
- const { buildChromeArgs: buildChromeArgs2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
113715
+ const { buildChromeArgs: buildChromeArgs2, analyzeClipMediaFit: analyzeClipMediaFit2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
113601
113716
  const browserGpuMode = process.env.PRODUCER_BROWSER_GPU_MODE === "software" ? "software" : "hardware";
113602
113717
  const chromeBrowser = await puppeteer.default.launch({
113603
113718
  headless: true,
@@ -113644,6 +113759,9 @@ async function validateInBrowser(projectDir, opts) {
113644
113759
  });
113645
113760
  await page.goto(server.url, { waitUntil: "domcontentloaded", timeout: 1e4 });
113646
113761
  await new Promise((r2) => setTimeout(r2, opts.timeout ?? 3e3));
113762
+ for (const w3 of await auditClipDurations(page, analyzeClipMediaFit2)) {
113763
+ warnings.push(w3);
113764
+ }
113647
113765
  if (opts.contrast) {
113648
113766
  contrast = await runContrastAudit(page);
113649
113767
  }
@@ -1 +1 @@
1
- import{g as P}from"./index-BSyZKYmH.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
1
+ import{g as P}from"./index-e4xyxg7-.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
@@ -1,4 +1,4 @@
1
- import{n as Qi}from"./index-BSyZKYmH.js";/*!
1
+ import{n as Qi}from"./index-e4xyxg7-.js";/*!
2
2
  * Copyright (c) 2026-present, Vanilagy and contributors
3
3
  *
4
4
  * This Source Code Form is subject to the terms of the Mozilla Public