mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(engine): skip unnecessary dimension pad (#2398)
This commit is contained in:
@@ -894,7 +894,17 @@ describe("buildEncoderArgs color space", () => {
|
||||
);
|
||||
const vfIdx = args.indexOf("-vf");
|
||||
expect(vfIdx).toBeGreaterThan(-1);
|
||||
expect(args[vfIdx + 1]).toContain("scale=in_range=pc:out_range=tv");
|
||||
expect(args[vfIdx + 1]).toBe("scale=in_range=pc:out_range=tv");
|
||||
});
|
||||
|
||||
it("adds the pad after range conversion for odd CPU output dimensions", () => {
|
||||
const args = buildEncoderArgs(
|
||||
{ ...baseOptions, height: 1081, codec: "h264", preset: "medium", quality: 23 },
|
||||
inputArgs,
|
||||
"out.mp4",
|
||||
);
|
||||
const vfIdx = args.indexOf("-vf");
|
||||
expect(args[vfIdx + 1]).toBe("scale=in_range=pc:out_range=tv,pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
});
|
||||
|
||||
it("prepends range conversion to VAAPI filter chain", () => {
|
||||
@@ -912,7 +922,14 @@ describe("buildEncoderArgs color space", () => {
|
||||
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 },
|
||||
{
|
||||
...baseOptions,
|
||||
height: 1081,
|
||||
codec: "h264",
|
||||
preset: "medium",
|
||||
quality: 23,
|
||||
useGpu: true,
|
||||
},
|
||||
inputArgs,
|
||||
"out.mp4",
|
||||
gpu,
|
||||
@@ -927,10 +944,21 @@ describe("buildEncoderArgs color space", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not require the pad filter for even GPU output dimensions", () => {
|
||||
const args = buildEncoderArgs(
|
||||
{ ...baseOptions, codec: "h264", preset: "medium", quality: 23, useGpu: true },
|
||||
inputArgs,
|
||||
"out.mp4",
|
||||
"videotoolbox",
|
||||
);
|
||||
expect(args).not.toContain("-vf");
|
||||
});
|
||||
|
||||
it("pads odd dimensions for 10-bit (yuv420p10le) GPU HDR encoding", () => {
|
||||
const args = buildEncoderArgs(
|
||||
{
|
||||
...baseOptions,
|
||||
height: 1081,
|
||||
codec: "h265",
|
||||
preset: "medium",
|
||||
quality: 23,
|
||||
|
||||
@@ -417,14 +417,22 @@ export function buildEncoderArgs(
|
||||
// encoder with no `-vf`. They hit the same "height not divisible by 2"
|
||||
// abort as libx264 on an odd-sized 4:2:0 canvas, so pad odd dimensions
|
||||
// up to even on the software side before the encode.
|
||||
const vf = withEvenDimensionPad("", pixelFormat);
|
||||
const vf = withEvenDimensionPad("", pixelFormat, options.width, options.height);
|
||||
if (vf) args.push("-vf", vf);
|
||||
} else {
|
||||
// Range conversion: Chrome screenshots are full-range RGB.
|
||||
// The scale filter handles both 8-bit and 10-bit correctly. Pad odd
|
||||
// dimensions up to even so libx264/libx265 (4:2:0) don't abort with
|
||||
// "height not divisible by 2" on an odd-sized composition canvas.
|
||||
args.push("-vf", withEvenDimensionPad("scale=in_range=pc:out_range=tv", pixelFormat));
|
||||
args.push(
|
||||
"-vf",
|
||||
withEvenDimensionPad(
|
||||
"scale=in_range=pc:out_range=tv",
|
||||
pixelFormat,
|
||||
options.width,
|
||||
options.height,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fixed timescale for consistent A/V timing across platforms.
|
||||
|
||||
@@ -164,6 +164,14 @@ describe("buildStreamingArgs", () => {
|
||||
expect(args[args.indexOf("-color_primaries:v") + 1]).toBe("bt709");
|
||||
expect(args[args.indexOf("-colorspace:v") + 1]).toBe("bt709");
|
||||
expect(args[args.indexOf("-color_range") + 1]).toBe("tv");
|
||||
expect(args[args.indexOf("-vf") + 1]).toBe("scale=in_range=pc:out_range=tv");
|
||||
});
|
||||
|
||||
it("adds the pad after range conversion for odd SDR output dimensions", () => {
|
||||
const args = buildStreamingArgs({ ...baseSdr, height: 1081 }, "/tmp/out.mp4");
|
||||
expect(args[args.indexOf("-vf") + 1]).toBe(
|
||||
"scale=in_range=pc:out_range=tv,pad=ceil(iw/2)*2:ceil(ih/2)*2",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,13 +323,18 @@ describe("buildStreamingArgs", () => {
|
||||
// even-dim pad (and only the pad, not the SW range scale) must be added.
|
||||
it("pads odd dimensions (no range scale) for non-VAAPI GPU encoding", () => {
|
||||
for (const gpu of ["nvenc", "videotoolbox", "qsv", "amf"] as const) {
|
||||
const args = buildStreamingArgs(baseGpu, "/tmp/out.mp4", gpu);
|
||||
const args = buildStreamingArgs({ ...baseGpu, height: 1081 }, "/tmp/out.mp4", gpu);
|
||||
const vfIdx = args.indexOf("-vf");
|
||||
expect(args[vfIdx + 1]).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
expect(args[vfIdx + 1]).not.toContain("scale=in_range");
|
||||
}
|
||||
});
|
||||
|
||||
it("does not require the pad filter for even GPU output dimensions", () => {
|
||||
const args = buildStreamingArgs(baseGpu, "/tmp/out.mp4", "videotoolbox");
|
||||
expect(args).not.toContain("-vf");
|
||||
});
|
||||
|
||||
it("prepends range conversion to VAAPI chain (nv12 covers even-dim)", () => {
|
||||
const args = buildStreamingArgs(baseGpu, "/tmp/out.mp4", "vaapi");
|
||||
const vfIdx = args.indexOf("-vf");
|
||||
|
||||
@@ -395,13 +395,21 @@ export function buildStreamingArgs(
|
||||
// encoder with no `-vf`. They hit the same "height not divisible by 2"
|
||||
// abort as libx264 on an odd-sized 4:2:0 canvas, so pad odd dimensions
|
||||
// up to even on the software side before the encode.
|
||||
const vf = withEvenDimensionPad("", pixelFormat);
|
||||
const vf = withEvenDimensionPad("", pixelFormat, options.width, options.height);
|
||||
if (vf) args.push("-vf", vf);
|
||||
} else {
|
||||
// Range conversion: Chrome screenshots are full-range RGB. Pad odd
|
||||
// dimensions up to even so libx264/libx265 (4:2:0) don't abort with
|
||||
// "height not divisible by 2" on an odd-sized composition canvas.
|
||||
args.push("-vf", withEvenDimensionPad("scale=in_range=pc:out_range=tv", pixelFormat));
|
||||
args.push(
|
||||
"-vf",
|
||||
withEvenDimensionPad(
|
||||
"scale=in_range=pc:out_range=tv",
|
||||
pixelFormat,
|
||||
options.width,
|
||||
options.height,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fixed timescale for consistent A/V timing across platforms.
|
||||
|
||||
@@ -25,6 +25,18 @@ describe("withEvenDimensionPad", () => {
|
||||
expect(withEvenDimensionPad("", "yuv420p")).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
});
|
||||
|
||||
it("omits the pad when known dimensions are already even", () => {
|
||||
expect(withEvenDimensionPad("", "yuv420p", 1920, 1080)).toBe("");
|
||||
expect(withEvenDimensionPad("scale=in_range=pc:out_range=tv", "yuv420p", 1920, 1080)).toBe(
|
||||
"scale=in_range=pc:out_range=tv",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the pad when either known dimension is odd", () => {
|
||||
expect(withEvenDimensionPad("", "yuv420p", 1921, 1080)).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
expect(withEvenDimensionPad("", "yuv420p", 1920, 1081)).toBe("pad=ceil(iw/2)*2:ceil(ih/2)*2");
|
||||
});
|
||||
|
||||
it("leaves the filter chain unchanged for alpha output (even in, unchanged)", () => {
|
||||
const vf = "scale=in_range=pc:out_range=tv";
|
||||
expect(withEvenDimensionPad(vf, "yuva444p10le")).toBe(vf);
|
||||
|
||||
@@ -36,10 +36,20 @@ export function requiresEvenDimensions(pixelFormat: string): boolean {
|
||||
|
||||
/**
|
||||
* Append the even-dimension pad to an FFmpeg `-vf` chain when the target pixel
|
||||
* format requires it. Returns the chain unchanged for formats that accept odd
|
||||
* dimensions, and returns just the pad when there is no existing chain.
|
||||
* format requires it. When both dimensions are known and already even, omit
|
||||
* the filter entirely so minimal FFmpeg builds do not need to provide `pad`.
|
||||
* Returns the chain unchanged for formats that accept odd dimensions, and
|
||||
* returns just the pad when there is no existing chain.
|
||||
*/
|
||||
export function withEvenDimensionPad(vfChain: string, pixelFormat: string): string {
|
||||
export function withEvenDimensionPad(
|
||||
vfChain: string,
|
||||
pixelFormat: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
): string {
|
||||
if (!requiresEvenDimensions(pixelFormat)) return vfChain;
|
||||
if (width !== undefined && height !== undefined && width % 2 === 0 && height % 2 === 0) {
|
||||
return vfChain;
|
||||
}
|
||||
return vfChain ? `${vfChain},${EVEN_DIMENSION_PAD}` : EVEN_DIMENSION_PAD;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user