@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 +6 -4
- package/dist/index.js +88 -28
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
const
|
|
7341
|
-
|
|
7342
|
-
const
|
|
7343
|
-
if (
|
|
7344
|
-
|
|
7345
|
-
|
|
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}-${
|
|
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.
|
|
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.
|
|
38
|
+
"@hyperframes/parsers": "0.8.183"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|