mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
fix(engine): pad odd output dimensions up to even for H.264/H.265 encode (#1802)
* fix(engine): pad odd output dimensions up to even for H.264/H.265 encode
A composition with an odd data-width or data-height (e.g. a custom 3:1
canvas at 1080x723) failed to encode to MP4. libx264/libx265 with 4:2:0
chroma subsampling (yuv420p, yuv420p10le) require both dimensions to be
even and abort before writing a packet:
[libx264] height not divisible by 2 (1080x723)
Error while opening encoder ... Invalid argument
Both the streaming encoder and the chunk encoder built the software
range-conversion filter ("scale=in_range=pc:out_range=tv") with no
even-dimension enforcement, so any odd-sized canvas reached libx264
unmodified and the whole render failed.
Add a shared withEvenDimensionPad helper that appends
pad=ceil(iw/2)*2:ceil(ih/2)*2 to the filter chain only for 4:2:0 pixel
formats. The pad rounds each odd dimension up by one pixel (a no-op when
already even) without scaling, so content is never resampled. Formats
that accept odd dimensions (ProRes 4444 yuva444p10le, VP9 yuva420p) are
excluded, so transparent/alpha output is untouched.
* fix(engine): extend even-dimension pad to GPU 4:2:0 encode paths
The odd-dimension pad added for libx264/libx265 only covered the software
encoder branches. nvenc, videotoolbox, qsv, and amf feed software frames
straight to the hardware encoder with no -vf chain, so an odd-sized 4:2:0
canvas on --gpu (or an auto-selected hardware encoder) reproduced the same
"height not divisible by 2" abort before any packet was written.
Add the even-dimension pad to the software-side -vf chain for those four
GPU paths in both the chunk and streaming encoders, reusing the shared
withEvenDimensionPad helper (the pad runs on CPU before the encode). vaapi
is left as-is: its existing format=nv12,hwupload conversion already aligns
odd dimensions before upload, so it is not double-padded. ProRes 4444 and
VP9 alpha stay untouched, exactly as the software fix excludes them.
nvenc/videotoolbox/qsv/amf arg construction is logic-tested (the pad filter
is asserted on the built arg list for 8-bit and 10-bit 4:2:0, with alpha
ProRes asserted padless); runtime hardware encode is not exercised here.
This commit is contained in:
@@ -904,16 +904,55 @@ describe("buildEncoderArgs color space", () => {
|
||||
expect(args[vfIdx + 1]).toBe("scale=in_range=pc:out_range=tv,format=nv12,hwupload");
|
||||
});
|
||||
|
||||
it("skips range conversion filter for non-VAAPI GPU encoding", () => {
|
||||
it("pads odd dimensions (no range scale) for non-VAAPI GPU encoding", () => {
|
||||
for (const gpu of ["nvenc", "videotoolbox", "qsv", "amf"] as const) {
|
||||
const args = buildEncoderArgs(
|
||||
{ ...baseOptions, codec: "h264", preset: "medium", quality: 23, useGpu: true },
|
||||
inputArgs,
|
||||
"out.mp4",
|
||||
gpu,
|
||||
);
|
||||
const vfIdx = args.indexOf("-vf");
|
||||
// 4:2:0 HW encode still aborts on odd dims, so the pad must be present —
|
||||
// but the range scale belongs to the SW path only.
|
||||
expect(args[vfIdx + 1]).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
expect(args[vfIdx + 1]).not.toContain("scale=in_range");
|
||||
// but still has color metadata
|
||||
expect(args).toContain("-colorspace:v");
|
||||
}
|
||||
});
|
||||
|
||||
it("pads odd dimensions for 10-bit (yuv420p10le) GPU HDR encoding", () => {
|
||||
const args = buildEncoderArgs(
|
||||
{ ...baseOptions, codec: "h264", preset: "medium", quality: 23, useGpu: true },
|
||||
{
|
||||
...baseOptions,
|
||||
codec: "h265",
|
||||
preset: "medium",
|
||||
quality: 23,
|
||||
useGpu: true,
|
||||
pixelFormat: "yuv420p10le",
|
||||
},
|
||||
inputArgs,
|
||||
"out.mp4",
|
||||
"nvenc",
|
||||
);
|
||||
expect(args[args.indexOf("-vf") + 1]).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
});
|
||||
|
||||
it("leaves alpha ProRes untouched (no even-dim pad)", () => {
|
||||
const args = buildEncoderArgs(
|
||||
{
|
||||
...baseOptions,
|
||||
codec: "prores",
|
||||
preset: "4",
|
||||
quality: 23,
|
||||
pixelFormat: "yuva444p10le",
|
||||
},
|
||||
inputArgs,
|
||||
"out.mov",
|
||||
);
|
||||
expect(args.indexOf("-vf")).toBe(-1);
|
||||
// but still has color metadata
|
||||
expect(args).toContain("-colorspace:v");
|
||||
expect(args.join(" ")).not.toContain("pad=");
|
||||
});
|
||||
|
||||
it("does not add color metadata for VP9", () => {
|
||||
|
||||
Reference in New Issue
Block a user