diff --git a/README.md b/README.md index 0d1fa9e..7a04fe9 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/src/converters/ffmpeg.ts b/src/converters/ffmpeg.ts index 8418e2d..454c633 100644 --- a/src/converters/ffmpeg.ts +++ b/src/converters/ffmpeg.ts @@ -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": - extraArgs.push("-c:v", "libx264"); + if (preferHardware) { + extraArgs.push("-c:v", "h264_nvenc"); + } else { + extraArgs.push("-c:v", "libx264"); + } break; case "h265": - extraArgs.push("-c:v", "libx265"); + if (preferHardware) { + extraArgs.push("-c:v", "hevc_nvenc"); + } else { + extraArgs.push("-c:v", "libx265"); + } break; case "h266": extraArgs.push("-c:v", "libx266"); diff --git a/tests/converters/ffmpeg.test.ts b/tests/converters/ffmpeg.test.ts index 6f0afc6..e190892 100644 --- a/tests/converters/ffmpeg.test.ts +++ b/tests/converters/ffmpeg.test.ts @@ -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";