mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
fix(cli): snapshot no longer transcodes every iPhone clip it never plays (#5755)
* fix(cli): snapshot no longer transcodes every iPhone clip it never plays snapshot served Studio's media proxies by default, so its page server transcoded every browser-hostile clip (every HEVC clip) in full at load. Snapshots show frames extracted with FFmpeg and never use those copies. Proxies are now served only with --proxy; hyperframes.json media.autoProxy no longer turns them on for snapshots. * docs: snapshot makes no media proxy unless asked
This commit is contained in:
@@ -186,7 +186,8 @@ asset and builds a bounded H.264 proxy automatically, cached under
|
||||
|
||||
`preview`, `play`, Studio, and published player pages use that proxy for
|
||||
playback. A render always decodes the original file, so nothing about final
|
||||
quality or color is touched.
|
||||
quality or color is touched. `snapshot` also reads frames from the original with
|
||||
FFmpeg, so it makes no proxy unless you pass `--proxy`.
|
||||
|
||||
`hyperframes lint` also flags the asset at info level (`hevc_preview_codec`) so
|
||||
you know a proxy is in play. Proxying is optional. Pass `--no-proxy` per
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { StaticProjectServer } from "../utils/staticProjectServer.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
injectMediaCodecMap: vi.fn(async (html: string) => html),
|
||||
resolveProxy: vi.fn(async () => "/unused-proxy-path"),
|
||||
}));
|
||||
|
||||
vi.mock("../utils/compositionServer.js", () => ({
|
||||
injectMediaCodecMap: mocks.injectMediaCodecMap,
|
||||
}));
|
||||
vi.mock("@hyperframes/studio-server/proxy-transcoder", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@hyperframes/studio-server/proxy-transcoder")>()),
|
||||
resolveProxy: mocks.resolveProxy,
|
||||
}));
|
||||
|
||||
import { serveCapturePage } from "./snapshot.js";
|
||||
|
||||
let server: StaticProjectServer | undefined;
|
||||
let dir: string | undefined;
|
||||
|
||||
afterEach(async () => {
|
||||
await server?.close();
|
||||
server = undefined;
|
||||
if (dir) rmSync(dir, { recursive: true, force: true });
|
||||
dir = undefined;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
/** A project whose hyperframes.json turns media proxies on, with an iPhone-style HEVC clip. */
|
||||
function hostileClipProject(): string {
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-snapshot-proxy-"));
|
||||
writeFileSync(join(dir, "hyperframes.json"), JSON.stringify({ media: { autoProxy: true } }));
|
||||
writeFileSync(join(dir, "clip.mov"), "fake hevc bytes");
|
||||
return dir;
|
||||
}
|
||||
|
||||
const html = '<html><head></head><body><video src="clip.mov"></video></body></html>';
|
||||
|
||||
describe("serveCapturePage", () => {
|
||||
it("starts no transcode of a hostile clip by default, whatever hyperframes.json says", async () => {
|
||||
server = await serveCapturePage(hostileClipProject(), html, undefined);
|
||||
const proxy = await fetch(`${server.url}clip.mov?hf-proxy=h264`);
|
||||
|
||||
expect(proxy.status).toBe(404);
|
||||
expect(mocks.injectMediaCodecMap).not.toHaveBeenCalled();
|
||||
expect(mocks.resolveProxy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("serves copies when --proxy asks for them", async () => {
|
||||
server = await serveCapturePage(hostileClipProject(), html, true);
|
||||
|
||||
expect(mocks.injectMediaCodecMap).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -83,11 +83,11 @@ describe("transparent snapshot capture", () => {
|
||||
expect(source).toContain('page.screenshot({ type: "png", omitBackground: true })');
|
||||
});
|
||||
|
||||
it("exposes --proxy/--no-proxy and forwards the override to the static server", () => {
|
||||
it("exposes --proxy and forwards it to the snapshot page server", () => {
|
||||
const source = readFileSync(new URL("./snapshot.ts", import.meta.url), "utf8");
|
||||
expect(source).toContain("proxy: {");
|
||||
expect(source).toContain("autoProxy: args.proxy as boolean | undefined");
|
||||
expect(source).toContain("opts.autoProxy");
|
||||
expect(source).toContain("serveCapturePage(projectDir, html, opts.autoProxy)");
|
||||
});
|
||||
|
||||
it("pairs every frame with the frame-exact reference frame under --against", () => {
|
||||
|
||||
@@ -463,6 +463,10 @@ export function computeSnapshotTimes(
|
||||
return { times: times.map(round), appendedTail: false };
|
||||
}
|
||||
|
||||
export function serveCapturePage(projectDir: string, html: string, proxy: boolean | undefined) {
|
||||
return serveStaticProjectHtml(projectDir, html, undefined, [], proxy === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render key frames from a composition as PNG screenshots.
|
||||
* The agent can Read these to verify its output visually.
|
||||
@@ -506,7 +510,7 @@ async function captureSnapshots(
|
||||
// Localize fonts (embed remote @font-face as data URIs, matching the render
|
||||
// path) so snapshots render the real font instead of a fallback sans.
|
||||
const html = await bundleWithLocalizedFonts(projectDir);
|
||||
const server = await serveStaticProjectHtml(projectDir, html, undefined, [], opts.autoProxy);
|
||||
const server = await serveCapturePage(projectDir, html, opts.autoProxy);
|
||||
|
||||
const savedPaths: string[] = [];
|
||||
|
||||
@@ -1052,7 +1056,7 @@ export default defineCommand({
|
||||
proxy: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Auto-transcode browser-hostile video codecs for snapshots (default: on; overrides hyperframes.json media.autoProxy)",
|
||||
"Serve browser-playable copies of hostile video codecs to the snapshot page (default: off; frames come from FFmpeg, and each copy transcodes the whole clip)",
|
||||
default: undefined,
|
||||
},
|
||||
"browser-gpu": {
|
||||
|
||||
Reference in New Issue
Block a user