mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
fix: render parity for transparent looped videos (#478)
## Summary - preserve alpha for render-injected video frames by detecting alpha streams with ffprobe and extracting alpha video frames as PNG - keep `<video loop>` semantics through static parsing, compiler duration resolution, browser media discovery, and render frame lookup - fail embedded preview startup before opening a broken browser page when the Studio bundle is missing - align snapshot frame injection with looped media timing and VP9 alpha extraction ## Why The Studio preview and rendered MP4 could disagree for timed transparent looped videos. The Comfy funding composition exposed two separate parity bugs: render-injected frames needed alpha-preserving PNG extraction, and the compiler was clamping a looped `data-duration="4"` video down to the 3.125s source duration. After the first source cycle, render lookup treated the video as inactive, hid the native video, and produced the blank polygon/glow the user saw around the rounded `0:03` mark. `hyperframes lint` and `hyperframes validate` did not catch this because they check syntax/load/console/accessibility, not preview-vs-render visual parity. This PR adds regression coverage for the compiler loop-duration path and frame lookup path. ## Verification - `bun run --filter @hyperframes/core test -- src/compiler/timingCompiler.test.ts src/compiler/htmlCompiler.test.ts` - `bun test packages/producer/src/services/htmlCompiler.test.ts` - `bun run --filter @hyperframes/engine test -- videoFrameExtractor ffprobe` - `bun run --filter @hyperframes/core typecheck` - `bun run --filter @hyperframes/engine typecheck` - `bun run --filter @hyperframes/producer typecheck` - `bun run --filter @hyperframes/cli typecheck` - `bun run lint` - `bun run format:check ...` on touched files - Comfy project: `node packages/cli/dist/cli.js validate` -> no console errors, 44 text elements pass WCAG AA - Comfy project patched render from source: `/tmp/comfy-render-compare/fixed6-comfy.mp4`, 1920x1080, 30fps, 21.8s, 654 frames - 3.00s-3.97s render contact sheet: `/tmp/comfy-render-compare/fixed6-window-contact.png` - targeted fixed render capture at 3.733s: `/tmp/comfy-render-compare/probe-capture-fixed/captured/frame_000112.jpg` - agent-browser Studio proof screenshot at 3.7s: `/tmp/comfy-render-compare/agent-browser-studio-3_7-fixed.png` - agent-browser-driven recording of 3s seek pass: `/tmp/comfy-render-compare/agent-browser-wysiwyg-3s-fixed.webm` Note: `bun run --filter @hyperframes/cli dev -- validate` is blocked in source mode by the existing `contrast-audit.browser.js` default-export loader issue; packaged `node packages/cli/dist/cli.js validate` passes for this project.
This commit is contained in:
@@ -23,11 +23,38 @@ import {
|
||||
// ── Path resolution ─────────────────────────────────────────────────────────
|
||||
|
||||
function resolveDistDir(): string {
|
||||
return resolveStudioBundle().dir;
|
||||
}
|
||||
|
||||
export interface StudioBundleResolution {
|
||||
dir: string;
|
||||
indexPath: string;
|
||||
available: boolean;
|
||||
checkedPaths: string[];
|
||||
}
|
||||
|
||||
export function resolveStudioBundle(): StudioBundleResolution {
|
||||
const builtPath = resolve(__dirname, "studio");
|
||||
if (existsSync(resolve(builtPath, "index.html"))) return builtPath;
|
||||
const builtIndex = resolve(builtPath, "index.html");
|
||||
if (existsSync(builtIndex)) {
|
||||
return { dir: builtPath, indexPath: builtIndex, available: true, checkedPaths: [builtIndex] };
|
||||
}
|
||||
const devPath = resolve(__dirname, "..", "..", "..", "studio", "dist");
|
||||
if (existsSync(resolve(devPath, "index.html"))) return devPath;
|
||||
return builtPath;
|
||||
const devIndex = resolve(devPath, "index.html");
|
||||
if (existsSync(devIndex)) {
|
||||
return {
|
||||
dir: devPath,
|
||||
indexPath: devIndex,
|
||||
available: true,
|
||||
checkedPaths: [builtIndex, devIndex],
|
||||
};
|
||||
}
|
||||
return {
|
||||
dir: builtPath,
|
||||
indexPath: builtIndex,
|
||||
available: false,
|
||||
checkedPaths: [builtIndex, devIndex],
|
||||
};
|
||||
}
|
||||
|
||||
function resolveRuntimePath(): string {
|
||||
@@ -348,7 +375,60 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
|
||||
app.get("*", (c) => {
|
||||
const indexPath = resolve(studioDir, "index.html");
|
||||
if (!existsSync(indexPath)) {
|
||||
return c.text("Studio not found. Rebuild with: pnpm run build", 500);
|
||||
return c.html(
|
||||
`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>HyperFrames Studio unavailable</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #0d0f14;
|
||||
color: #eef2f7;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
main {
|
||||
width: min(560px, calc(100vw - 48px));
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 8px;
|
||||
padding: 28px;
|
||||
background: #151923;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 18px;
|
||||
color: #aab3c2;
|
||||
line-height: 1.5;
|
||||
}
|
||||
code {
|
||||
display: block;
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
background: #090b10;
|
||||
color: #8ff0c2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Studio bundle missing</h1>
|
||||
<p>The preview server started, but this CLI build does not contain the Studio assets.</p>
|
||||
<code>pnpm run build</code>
|
||||
</main>
|
||||
</body>
|
||||
</html>`,
|
||||
500,
|
||||
);
|
||||
}
|
||||
return c.html(readFileSync(indexPath, "utf-8"));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user