mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
fix(cli): extract snapshot video frames from remote http(s) srcs
`hyperframes snapshot` works around Chrome-headless's inability to seek `<video>` elements by extracting a frame via FFmpeg and injecting it as an overlay. That path only resolved `<video src>` to a project-LOCAL file and skipped everything else — so a composition whose embedded `<video>` points at a remote http(s) URL (e.g. an S3-hosted clip embedded by an upstream agent) rendered as a blank box in every snapshot, while `render` (which plays the element in-browser) showed it fine. Add a remote fallback: when the src doesn't resolve to a project-local file but is an http(s) URL, pass the absolute URL straight to FFmpeg (it reads http(s) input directly). Local-first is preserved (fast, sandboxed); the existing 30s extract timeout bounds remote fetches. Verified on a real composition: remote-src snapshot was blank, local-src rendered; `ffmpeg -ss N -i <https-url> -frames:v 1` extracts in ~0.5s. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5915590b06
commit
1f377f732b
@@ -355,23 +355,30 @@ async function captureSnapshots(
|
||||
|
||||
const updates: Array<{ videoId: string; dataUri: string }> = [];
|
||||
for (const v of active) {
|
||||
let filePath: string | null = null;
|
||||
// Resolve the <video> src to an FFmpeg input. Prefer a project-local
|
||||
// file (fast, sandboxed); fall back to the absolute http(s) URL for
|
||||
// remote assets (e.g. an S3-hosted clip embedded by an upstream agent)
|
||||
// — FFmpeg reads http(s) input directly, and Chrome-headless can't seek
|
||||
// it either, so without this those videos render blank in snapshots.
|
||||
let ffmpegInput: string | null = null;
|
||||
try {
|
||||
const url = new URL(v.src);
|
||||
const decodedPath = decodeURIComponent(url.pathname).replace(/^\//, "");
|
||||
const candidate = resolve(projectDir, decodedPath);
|
||||
const rel = relative(projectDir, candidate);
|
||||
if (!rel.startsWith("..") && !isAbsolute(rel) && existsSync(candidate)) {
|
||||
filePath = candidate;
|
||||
ffmpegInput = candidate;
|
||||
} else if (url.protocol === "http:" || url.protocol === "https:") {
|
||||
ffmpegInput = url.href;
|
||||
}
|
||||
} catch {
|
||||
/* unresolvable src (e.g. blob:, data:) — skip */
|
||||
}
|
||||
if (!filePath) continue;
|
||||
if (!ffmpegInput) continue;
|
||||
const png = await extractVideoFrameToBuffer(
|
||||
filePath,
|
||||
ffmpegInput,
|
||||
Math.max(0, v.relTime),
|
||||
await shouldUseVp9AlphaDecoder(filePath),
|
||||
await shouldUseVp9AlphaDecoder(ffmpegInput),
|
||||
);
|
||||
if (!png) continue;
|
||||
updates.push({
|
||||
|
||||
Reference in New Issue
Block a user