mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(lint): a media src whose file name holds # or ? says to write it percent-encoded (#6495)
* fix(lint): a media src whose file name holds # or ? says to write it percent-encoded * refactor(lint): the url-safe hint encodes with the shared encodeUrlPath
This commit is contained in:
@@ -1236,3 +1236,40 @@ describe("sub-composition files", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a media file whose own name holds # or ?", () => {
|
||||
const name = "track #1.mp3";
|
||||
|
||||
async function findingFor(tag: string, code: string) {
|
||||
const project = makeProject(`<html><body>
|
||||
<div data-composition-id="main" data-width="1920" data-height="1080" data-start="0" data-duration="10"></div>
|
||||
${tag}
|
||||
</body></html>`);
|
||||
mkdirSync(join(project, "assets"), { recursive: true });
|
||||
writeFileSync(join(project, "assets", name), "");
|
||||
writeFileSync(join(project, "assets", "pic #2.png"), "");
|
||||
const { results } = await lintProject(project);
|
||||
return results.flatMap((entry) => entry.result.findings).find((f) => f.code === code);
|
||||
}
|
||||
|
||||
it("tells an audio src the name is cut at the # and how to write it", async () => {
|
||||
const finding = await findingFor(
|
||||
`<audio id="a1" class="clip" data-start="0" data-duration="3" data-track-index="10" src="assets/${name}"></audio>`,
|
||||
"audio_src_not_found",
|
||||
);
|
||||
expect(finding?.fixHint).toContain('"assets/track%20%231.mp3"');
|
||||
});
|
||||
|
||||
it("tells an image src the same", async () => {
|
||||
const finding = await findingFor(`<img src="assets/pic #2.png" />`, "missing_local_asset");
|
||||
expect(finding?.fixHint).toContain('"assets/pic%20%232.png"');
|
||||
});
|
||||
|
||||
it("keeps the add-the-file hint when no file by that name exists", async () => {
|
||||
const finding = await findingFor(
|
||||
`<audio id="a1" class="clip" data-start="0" data-duration="3" data-track-index="10" src="assets/nope #1.mp3"></audio>`,
|
||||
"audio_src_not_found",
|
||||
);
|
||||
expect(finding?.fixHint).toContain("Add the file");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
resolveLocalAssetCandidates,
|
||||
resolveProjectRelativeSrc,
|
||||
} from "@hyperframes/parsers/asset-resolution";
|
||||
import { encodeUrlPath } from "@hyperframes/parsers";
|
||||
import {
|
||||
collectLocalVideoCandidates,
|
||||
lintHevcPreviewCodec,
|
||||
@@ -385,6 +386,18 @@ function lintProjectAudioFiles(
|
||||
return findings;
|
||||
}
|
||||
|
||||
/** The src that loads a project file whose own name holds `#` or `?`, which a URL cuts the name at; null when the raw
|
||||
* src names no such file. */
|
||||
function urlSafeSrcOf(projectDir: string, rootRelative: string): string | null {
|
||||
if (!/[?#]/.test(rootRelative)) return null;
|
||||
const literal = resolve(projectDir, rootRelative.replace(/^\//, ""));
|
||||
if (!existsSync(literal)) return null;
|
||||
return encodeUrlPath(rootRelative);
|
||||
}
|
||||
|
||||
const urlSafeHint = (safe: string) =>
|
||||
`The file exists, but a "#" or "?" in a src ends the file name there. Write the src as "${safe}".`;
|
||||
|
||||
function lintAudioSrcNotFound(
|
||||
projectDir: string,
|
||||
htmlSources: HtmlSource[],
|
||||
@@ -394,6 +407,7 @@ function lintAudioSrcNotFound(
|
||||
const audioSrcRe = mediaSrcTagRe("audio");
|
||||
|
||||
const missingByFile = new Map<string, Set<string>>();
|
||||
const urlSafe = new Map<string, string>();
|
||||
for (const { html, compSrcPath } of htmlSources) {
|
||||
const scannable = maskNonScannableRanges(html);
|
||||
let match: RegExpExecArray | null;
|
||||
@@ -405,6 +419,8 @@ function lintAudioSrcNotFound(
|
||||
? rewriteAssetPath(compSrcPath, src, (path) => existsSync(join(projectDir, path)))
|
||||
: src;
|
||||
if (!existsSync(resolveProjectRelativeSrc(rootRelative, projectDir))) {
|
||||
const safe = urlSafeSrcOf(projectDir, rootRelative);
|
||||
if (safe) urlSafe.set(src, safe);
|
||||
const file = sourceFile(projectDir, compSrcPath);
|
||||
missingByFile.set(file, (missingByFile.get(file) ?? new Set()).add(src));
|
||||
}
|
||||
@@ -419,9 +435,11 @@ function lintAudioSrcNotFound(
|
||||
file,
|
||||
message: `<audio> element references file(s) not found in the project: ${unique.join(", ")}. The rendered video will be silent.`,
|
||||
fixHint:
|
||||
unique.length === 1
|
||||
? `Add the file "${unique[0]}" to the project directory, or update the src attribute to point to an existing file.`
|
||||
: `Add the missing files to the project directory, or update the src attributes to point to existing files.`,
|
||||
unique.length === 1 && urlSafe.has(unique[0]!)
|
||||
? urlSafeHint(urlSafe.get(unique[0]!)!)
|
||||
: unique.length === 1
|
||||
? `Add the file "${unique[0]}" to the project directory, or update the src attribute to point to an existing file.`
|
||||
: `Add the missing files to the project directory, or update the src attributes to point to existing files.`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -441,6 +459,7 @@ function lintMissingLocalAsset(
|
||||
string,
|
||||
{ file: string; tagName: string; byResolved: Map<string, string> }
|
||||
>();
|
||||
const urlSafe = new Map<string, string>();
|
||||
|
||||
for (const { html, compSrcPath } of htmlSources) {
|
||||
const scannable = maskNonScannableRanges(html);
|
||||
@@ -460,6 +479,14 @@ function lintMissingLocalAsset(
|
||||
const resolvedAsset = resolveExistingLocalAsset(projectDir, rootRelative);
|
||||
if (resolvedAsset) continue;
|
||||
|
||||
const rawRoot = rawSrc.trim();
|
||||
const safe = urlSafeSrcOf(
|
||||
projectDir,
|
||||
compSrcPath
|
||||
? rewriteAssetPath(compSrcPath, rawRoot, (path) => existsSync(join(projectDir, path)))
|
||||
: rawRoot,
|
||||
);
|
||||
if (safe) urlSafe.set(src, safe);
|
||||
const resolvedKey = resolve(projectDir, rootRelative);
|
||||
const file = sourceFile(projectDir, compSrcPath);
|
||||
const bucketKey = `${file}\0${tagName}`;
|
||||
@@ -482,12 +509,14 @@ function lintMissingLocalAsset(
|
||||
`<${tagName}> element references local file(s) not found in the project: ${unique.join(", ")}. ` +
|
||||
"The renderer will silently skip these and produce a video with missing visuals.",
|
||||
fixHint:
|
||||
unique.length === 1
|
||||
? `Add "${unique[0]}" to the project directory, or update the src attribute to point to an existing file. ` +
|
||||
"Common cause: captured asset filenames are unreliable (heygen-logo.svg often contains Google, nvidia-logo.svg may contain Autodesk, etc.). " +
|
||||
"Open the contact sheets and verify the file actually exists at this path before referencing it."
|
||||
: "Add the missing files to the project directory, or update the src attributes to point to existing files. " +
|
||||
"Captured asset filenames are unreliable. Verify against capture/contact-sheets/ and capture/extracted/asset-descriptions.md.",
|
||||
unique.length === 1 && urlSafe.has(unique[0]!)
|
||||
? urlSafeHint(urlSafe.get(unique[0]!)!)
|
||||
: unique.length === 1
|
||||
? `Add "${unique[0]}" to the project directory, or update the src attribute to point to an existing file. ` +
|
||||
"Common cause: captured asset filenames are unreliable (heygen-logo.svg often contains Google, nvidia-logo.svg may contain Autodesk, etc.). " +
|
||||
"Open the contact sheets and verify the file actually exists at this path before referencing it."
|
||||
: "Add the missing files to the project directory, or update the src attributes to point to existing files. " +
|
||||
"Captured asset filenames are unreliable. Verify against capture/contact-sheets/ and capture/extracted/asset-descriptions.md.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user