feat: add NVENC hardware encoding support for FFmpeg

Add FFMPEG_PREFER_HARDWARE env var to enable hardware acceleration
Use h264_nvenc and hevc_nvenc encoders when hardware preferred
Fall back to software encoders (libx264/libx265) when disabled
Add comprehensive tests for hardware/software encoding modes
Update README with new environment variable documentation
This enables GPU-accelerated video encoding for better performance on systems with NVIDIA GPUs.
This commit is contained in:
rob_otman 2025-12-17 14:12:13 -07:00
parent 911587ea68
commit 6cca431a76
3 changed files with 75 additions and 2 deletions

View file

@ -95,6 +95,7 @@ All are optional, JWT_SECRET is recommended to be set.
| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
| FFMPEG_ARGS | | Arguments to pass to the input file of ffmpeg, e.g. `-hwaccel vaapi`. See https://github.com/C4illin/ConvertX/issues/190 for more info about hw-acceleration. |
| FFMPEG_OUTPUT_ARGS | | Arguments to pass to the output of ffmpeg, e.g. `-preset veryfast` |
| FFMPEG_PREFER_HARDWARE | false | Use hardware encoders (NVENC, VAAPI, etc.) when available instead of software encoders for h264/h265 formats |
| HIDE_HISTORY | false | Hide the history page |
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |

View file

@ -712,15 +712,27 @@ export async function convert(
const split = convertTo.split(".");
const codec_short = split[0];
// Check if hardware encoding is preferred (NVENC, VAAPI, etc.)
const preferHardware = process.env.FFMPEG_PREFER_HARDWARE === "true" ||
process.env.FFMPEG_PREFER_HARDWARE === "1";
switch (codec_short) {
case "av1":
extraArgs.push("-c:v", "libaom-av1");
break;
case "h264":
if (preferHardware) {
extraArgs.push("-c:v", "h264_nvenc");
} else {
extraArgs.push("-c:v", "libx264");
}
break;
case "h265":
if (preferHardware) {
extraArgs.push("-c:v", "hevc_nvenc");
} else {
extraArgs.push("-c:v", "libx265");
}
break;
case "h266":
extraArgs.push("-c:v", "libx266");

View file

@ -121,6 +121,66 @@ test("uses libx266 for h266.mp4", async () => {
expect(loggedMessage).toBe("stdout: Fake stdout");
});
test("uses h264_nvenc for h264.mp4 when hardware preferred", async () => {
process.env.FFMPEG_PREFER_HARDWARE = "true";
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "h264_nvenc"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
delete process.env.FFMPEG_PREFER_HARDWARE;
});
test("uses hevc_nvenc for h265.mp4 when hardware preferred", async () => {
process.env.FFMPEG_PREFER_HARDWARE = "true";
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};
await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "hevc_nvenc"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
delete process.env.FFMPEG_PREFER_HARDWARE;
});
test("uses libx264 for h264.mp4 when hardware not preferred", async () => {
process.env.FFMPEG_PREFER_HARDWARE = "false";
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"]));
expect(loggedMessage).toBe("stdout: Fake stdout");
delete process.env.FFMPEG_PREFER_HARDWARE;
});
test("respects FFMPEG_ARGS", async () => {
process.env.FFMPEG_ARGS = "-hide_banner -y";