mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
fix(cli): accept 1–3-frame short WebMs + add direct pixel-probe tests
Address Miguel's R1 blocker + Rames/Miga's testability nit: - The `-frames:v 3` decode samples AT MOST 3 frames; a legitimate 1- or 2-frame WebM (256 or 512 bytes) was returned as `undefined` (probe failure), silently skipping the advisory even when every available pixel was opaque. Accept any positive whole-frame byte count ≤ 768 (multiples of 256), distinguishing successful short-EOF from partial/ malformed decode. - Export `sampledAlphaIsFullyOpaque` and add 11 direct tests covering: 3/2/1-frame opaque decodes → true; transparent pixel at pos 0 or final byte → false (guards the alpha-byte stride); non-frame-multiple / over-3-frame / zero byte counts → undefined; execFileSync throw → undefined; findFFmpeg missing → undefined; and one args-shape guard pinning the load-bearing `-c:v libvpx-vp9` before `-i` (without which the default decoder silently discards VP9 alpha and the whole check would false-positive on genuinely-transparent WebMs). - Update advisory wording from "3 sampled decoded frames" to "every sampled decoded pixel" so the message is honest for short WebMs. 18/18 tests pass locally under `vitest run`.
This commit is contained in:
@@ -12,11 +12,11 @@ export interface WebmAlphaProbe {
|
||||
/** True when the VP9 stream declares the alpha sidecar (ALPHA_MODE=1 tag). */
|
||||
alphaMode: boolean;
|
||||
/**
|
||||
* When true, the tag says alpha but 3 sampled decoded frames report every
|
||||
* pixel at alpha=255 — either the composition has no transparent regions in
|
||||
* the samples, or libvpx-vp9 wrote the tag without emitting the alpha side
|
||||
* data (a known Windows-build quirk). Undefined when the pixel-level probe
|
||||
* couldn't run (no ffmpeg, decode error, unexpected byte count) — an
|
||||
* When true, the tag says alpha but every decoded sample byte reads
|
||||
* alpha=255 — either the composition has no transparent regions in the
|
||||
* samples, or libvpx-vp9 wrote the tag without emitting the alpha side data
|
||||
* (a known Windows-build quirk). Undefined when the pixel-level probe
|
||||
* couldn't run (no ffmpeg, decode error, malformed byte count) — an
|
||||
* inconclusive probe is not a warning trigger.
|
||||
*/
|
||||
sampledAlphaFullyOpaque?: boolean;
|
||||
@@ -52,8 +52,8 @@ export function webmAlphaAdvisory(format: string, probe: WebmAlphaProbe): string
|
||||
}
|
||||
if (probe.sampledAlphaFullyOpaque) {
|
||||
return (
|
||||
"The WebM declares alpha (ALPHA_MODE=1) but 3 sampled decoded frames read " +
|
||||
"alpha=255 everywhere. This may be intentional (the composition has no transparent " +
|
||||
"The WebM declares alpha (ALPHA_MODE=1) but every sampled decoded pixel " +
|
||||
"reads alpha=255. This may be intentional (the composition has no transparent " +
|
||||
"regions in the samples) OR your ffmpeg/libvpx-vp9 build wrote the tag without " +
|
||||
"emitting the alpha side data — a known Windows-build quirk. To rule it out, " +
|
||||
"re-render with --format mov (ProRes 4444), or with --format png-sequence and " +
|
||||
@@ -116,13 +116,26 @@ function probeWebmAlpha(filePath: string): WebmAlphaProbe {
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the libvpx-vp9 decoder (default decoder silently discards VP9 alpha
|
||||
* — see docs/guides/rendering.mdx) and sample 3 frames at 8x8 rgba. Returns
|
||||
* `true` iff every alpha byte across all samples is 255, `false` when any
|
||||
* pixel shows partial/full transparency, `undefined` if the probe couldn't
|
||||
* run (no ffmpeg, decode error, unexpected byte count).
|
||||
* Bytes per sampled frame at 8x8 rgba: 8 * 8 * 4 = 256. `-frames:v 3` samples
|
||||
* AT MOST 3 frames — a legitimate 1-frame WebM (a still) yields 256 bytes and
|
||||
* a 2-frame yields 512, both valid opaque samples that must be evaluated.
|
||||
*/
|
||||
function sampledAlphaIsFullyOpaque(filePath: string): boolean | undefined {
|
||||
const BYTES_PER_SAMPLE_FRAME = 8 * 8 * 4;
|
||||
const MAX_SAMPLE_BYTES = BYTES_PER_SAMPLE_FRAME * 3;
|
||||
|
||||
/**
|
||||
* Force the libvpx-vp9 decoder (default decoder silently discards VP9 alpha
|
||||
* — see docs/guides/rendering.mdx) and sample up to 3 frames at 8x8 rgba.
|
||||
* Returns `true` iff every alpha byte across all sampled frames is 255,
|
||||
* `false` when any pixel shows partial/full transparency, `undefined` if the
|
||||
* probe couldn't run (no ffmpeg, decode error, or the byte count is not a
|
||||
* positive whole-frame multiple ≤ 768 — anything else is a malformed decode,
|
||||
* not a signal).
|
||||
*
|
||||
* Exported for direct unit testing; the pixel-level contract is too load-
|
||||
* bearing to only exercise through `webmAlphaAdvisory`.
|
||||
*/
|
||||
export function sampledAlphaIsFullyOpaque(filePath: string): boolean | undefined {
|
||||
const ffmpegPath = findFFmpeg();
|
||||
if (!ffmpegPath) return undefined;
|
||||
try {
|
||||
@@ -147,9 +160,13 @@ function sampledAlphaIsFullyOpaque(filePath: string): boolean | undefined {
|
||||
],
|
||||
{ timeout: 30_000, maxBuffer: 4096, stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
// 8*8 rgba * 3 frames = 768 bytes; require full frame count for a
|
||||
// reliable verdict (silent short-decode is a probe failure, not a signal).
|
||||
if (buf.length !== 768) return undefined;
|
||||
if (
|
||||
buf.length === 0 ||
|
||||
buf.length > MAX_SAMPLE_BYTES ||
|
||||
buf.length % BYTES_PER_SAMPLE_FRAME !== 0
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
for (let i = 3; i < buf.length; i += 4) {
|
||||
if (buf[i] !== 255) return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user