@hyperframes/lint 0.8.180 → 0.8.183

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/browser.js CHANGED
@@ -115,11 +115,13 @@ function readAttr(tagSource, attr) {
115
115
  function hasAttrName(tagSource, attr) {
116
116
  return readDecodedAttr(tagSource, attr) !== null;
117
117
  }
118
+ function isAudibleVideo(attr) {
119
+ if (attr("muted") !== null) return false;
120
+ const declared = attr("data-has-audio");
121
+ return declared === null || declared === "true";
122
+ }
118
123
  function isAudibleVideoTag(tagSource) {
119
- if (hasAttrName(tagSource, "muted")) return false;
120
- if (!hasAttrName(tagSource, "data-has-audio")) return true;
121
- const declared = readAttr(tagSource, "data-has-audio");
122
- return declared === "true";
124
+ return isAudibleVideo((name) => readDecodedAttr(tagSource, name));
123
125
  }
124
126
  function mediaTimeWindow(tagSource) {
125
127
  const start = Number(readAttr(tagSource, "data-start"));
package/dist/index.js CHANGED
@@ -115,11 +115,13 @@ function readAttr(tagSource, attr) {
115
115
  function hasAttrName(tagSource, attr) {
116
116
  return readDecodedAttr(tagSource, attr) !== null;
117
117
  }
118
+ function isAudibleVideo(attr) {
119
+ if (attr("muted") !== null) return false;
120
+ const declared = attr("data-has-audio");
121
+ return declared === null || declared === "true";
122
+ }
118
123
  function isAudibleVideoTag(tagSource) {
119
- if (hasAttrName(tagSource, "muted")) return false;
120
- if (!hasAttrName(tagSource, "data-has-audio")) return true;
121
- const declared = readAttr(tagSource, "data-has-audio");
122
- return declared === "true";
124
+ return isAudibleVideo((name) => readDecodedAttr(tagSource, name));
123
125
  }
124
126
  function mediaTimeWindow(tagSource) {
125
127
  const start = Number(readAttr(tagSource, "data-start"));
@@ -6652,6 +6654,17 @@ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
6652
6654
  import { dirname as dirname2, extname, join as join3, relative as relative2, resolve } from "path";
6653
6655
  import { rewriteAssetPath as rewriteAssetPath2 } from "@hyperframes/parsers/asset-paths";
6654
6656
  import { spansOverlap } from "@hyperframes/parsers/time-spans";
6657
+ import {
6658
+ compositionOriginSeconds,
6659
+ cutToHostSlots,
6660
+ MEDIA_START_BASIS_ATTR,
6661
+ resolveMediaStartSeconds
6662
+ } from "@hyperframes/parsers/media-timing";
6663
+ import { parseNumeric as parseNumeric2 } from "@hyperframes/parsers/composition-contract";
6664
+ import {
6665
+ resolveReferencedDuration,
6666
+ resolveReferencedStart
6667
+ } from "@hyperframes/parsers/reference-resolver";
6655
6668
  import { checkSubCompositionUsability } from "@hyperframes/parsers/sub-composition-validity";
6656
6669
  import { parseHTML as parseHTML3 } from "linkedom";
6657
6670
  import {
@@ -7112,7 +7125,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7112
7125
  ...lintTextureMaskAssetNotFound(projectDir, allHtmlSources),
7113
7126
  ...!entryFile ? lintMultipleRootCompositions(projectDir) : [],
7114
7127
  ...!entryFile ? lintBlankRootWithStandaloneComposition(rootHtml, allHtmlSources) : [],
7115
- ...lintDuplicateAudioTracks(allHtmlSources),
7128
+ ...lintDuplicateAudioTracks(projectDir, rootHtml),
7116
7129
  ...lintMissingOrEmptySubComposition(projectDir, rootHtml),
7117
7130
  ...await lintVideoMediaStartPastEof(projectDir, allHtmlSources),
7118
7131
  ...await lintHevcPreviewCodec(collectLocalVideoCandidates(projectDir, allHtmlSources))
@@ -7321,30 +7334,77 @@ function lintMultipleRootCompositions(projectDir) {
7321
7334
  }
7322
7335
  return findings;
7323
7336
  }
7324
- function lintDuplicateAudioTracks(htmlSources) {
7325
- const findings = [];
7326
- const tracks = [];
7327
- const seen = /* @__PURE__ */ new Set();
7328
- for (const { html } of htmlSources) {
7329
- const soundTagRe = /<(?:audio|video)\b[^>]*>/gi;
7330
- let match;
7331
- while ((match = soundTagRe.exec(html)) !== null) {
7332
- const tag = match[0];
7333
- if (/^<video/i.test(tag) && !isAudibleVideoTag(tag)) continue;
7334
- const trackStr = readAttr(tag, "data-track-index");
7335
- const startStr = readAttr(tag, "data-start");
7336
- const durStr = readAttr(tag, "data-duration");
7337
- const src = readDecodedAttr(tag, "src") || "unknown";
7338
- if (!trackStr || !startStr) continue;
7339
- const trackIndex = parseInt(trackStr, 10);
7340
- const start = parseFloat(startStr);
7341
- const duration = durStr ? parseFloat(durStr) : Infinity;
7342
- const key = `${src}:${start}:${duration}:${trackIndex}`;
7343
- if (seen.has(key)) continue;
7344
- seen.add(key);
7345
- tracks.push({ trackIndex, start, end: start + duration, src });
7337
+ var timingFile = (doc) => ({ doc, starts: /* @__PURE__ */ new Map() });
7338
+ var localStart = (file, el) => resolveReferencedStart(file.doc, el, file.starts, /* @__PURE__ */ new Set());
7339
+ var localDuration = (file, el) => resolveReferencedDuration(file.doc, el, file.starts, /* @__PURE__ */ new Set());
7340
+ function collectMainTimelineAudio(projectDir, rootHtml) {
7341
+ const spans = [];
7342
+ const mount = (html, at) => {
7343
+ const { document: document2 } = parseHTML3(html);
7344
+ const root = querySelectorAllIncludingTemplates(document2, "[data-composition-id]")[0];
7345
+ if (root) visit(root, { ...at, file: timingFile(document2) });
7346
+ };
7347
+ const visit = (parent, at) => {
7348
+ for (const el of parent.children) {
7349
+ if (el.tagName === "TEMPLATE") {
7350
+ visit(el.content, at);
7351
+ continue;
7352
+ }
7353
+ const span = audioSpanOf(el, at.file, at.origin, at.slots);
7354
+ if (span) spans.push(span);
7355
+ const scope = nestedScopeOf(el, at.file, at.origin, at.slots);
7356
+ if (!scope) {
7357
+ visit(el, at);
7358
+ continue;
7359
+ }
7360
+ const inner = { ...at, ...scope };
7361
+ const src = el.getAttribute("data-composition-src")?.trim();
7362
+ const file = src && !isUnresolvedAssetPlaceholder2(src) && !isRemoteOrInlineUrl2(src);
7363
+ const filePath = file ? resolve(projectDir, src) : null;
7364
+ if (filePath && !at.mounting.includes(filePath)) {
7365
+ const read = readProjectFile2(filePath);
7366
+ if (read.kind === "file")
7367
+ mount(read.text, { ...inner, mounting: [...at.mounting, filePath] });
7368
+ }
7369
+ visit(el, inner);
7346
7370
  }
7371
+ };
7372
+ const { document } = parseHTML3(rootHtml);
7373
+ visit(document, { file: timingFile(document), origin: 0, slots: [], mounting: [] });
7374
+ return spans;
7375
+ }
7376
+ function nestedScopeOf(el, file, origin, slots) {
7377
+ if (!el.hasAttribute("data-composition-id") && !el.hasAttribute("data-composition-src")) {
7378
+ return null;
7379
+ }
7380
+ const start = origin + localStart(file, el);
7381
+ const duration = localDuration(file, el);
7382
+ const window = duration === null ? [] : [{ start, end: start + duration }];
7383
+ return { origin: compositionOriginSeconds(start, el), slots: [...slots, ...window] };
7384
+ }
7385
+ function audioSpanOf(el, file, origin, slots) {
7386
+ const tag = el.tagName.toLowerCase();
7387
+ if (tag !== "audio" && !(tag === "video" && isAudibleVideo((name) => el.getAttribute(name)))) {
7388
+ return null;
7347
7389
  }
7390
+ const trackStr = el.getAttribute("data-track-index");
7391
+ if (!trackStr || !el.hasAttribute("data-start")) return null;
7392
+ const start = resolveMediaStartSeconds({
7393
+ authoredStart: parseNumeric2(el.getAttribute("data-start")),
7394
+ hostStart: origin,
7395
+ hasAutoStart: el.hasAttribute("data-hf-auto-start"),
7396
+ basis: el.getAttribute(MEDIA_START_BASIS_ATTR),
7397
+ ordinaryStart: () => origin + localStart(file, el)
7398
+ });
7399
+ const duration = parseNumeric2(el.getAttribute("data-duration")) ?? localDuration(file, el);
7400
+ const kept = cutToHostSlots({ start, end: start + (duration ?? Infinity) }, slots);
7401
+ if (kept.end <= kept.start) return null;
7402
+ return { trackIndex: parseInt(trackStr, 10), ...kept, src: el.getAttribute("src") || "unknown" };
7403
+ }
7404
+ var formatSeconds = (t) => Number.isFinite(t) ? String(Number(t.toFixed(2))) : "end";
7405
+ function lintDuplicateAudioTracks(projectDir, rootHtml) {
7406
+ const findings = [];
7407
+ const tracks = collectMainTimelineAudio(projectDir, rootHtml);
7348
7408
  for (let i = 0; i < tracks.length; i++) {
7349
7409
  for (let j = i + 1; j < tracks.length; j++) {
7350
7410
  const a = tracks[i];
@@ -7354,7 +7414,7 @@ function lintDuplicateAudioTracks(htmlSources) {
7354
7414
  findings.push({
7355
7415
  code: "duplicate_audio_track",
7356
7416
  severity: "warning",
7357
- message: `Multiple audible <audio>/<video> elements on track ${a.trackIndex} overlap (${a.src} at ${a.start}-${Number.isFinite(a.end) ? a.end.toFixed(1) : "end"}s, ${b.src} at ${b.start}-${Number.isFinite(b.end) ? b.end.toFixed(1) : "end"}s). This causes layered audio playback.`,
7417
+ message: `Multiple audible <audio>/<video> elements on track ${a.trackIndex} overlap (${a.src} at ${formatSeconds(a.start)}-${formatSeconds(a.end)}s, ${b.src} at ${formatSeconds(b.start)}-${formatSeconds(b.end)}s). This causes layered audio playback.`,
7358
7418
  fixHint: "Use non-overlapping time windows or different track indices."
7359
7419
  });
7360
7420
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.180",
3
+ "version": "0.8.183",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -35,7 +35,7 @@
35
35
  "linkedom": "^0.18.12",
36
36
  "postcss": "^8.5.8",
37
37
  "postcss-selector-parser": "^7.1.4",
38
- "@hyperframes/parsers": "0.8.180"
38
+ "@hyperframes/parsers": "0.8.183"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",