diff --git a/packages/compositor-darwin-arm64/libavcodec.dylib b/packages/compositor-darwin-arm64/libavcodec.dylib
index 847a686c1f9..29e996b5c7b 100755
Binary files a/packages/compositor-darwin-arm64/libavcodec.dylib and b/packages/compositor-darwin-arm64/libavcodec.dylib differ
diff --git a/packages/compositor-darwin-arm64/remotion b/packages/compositor-darwin-arm64/remotion
index ba1e17cfdff..5f7f4b4326a 100755
Binary files a/packages/compositor-darwin-arm64/remotion and b/packages/compositor-darwin-arm64/remotion differ
diff --git a/packages/compositor-darwin-x64/libavcodec.dylib b/packages/compositor-darwin-x64/libavcodec.dylib
index 07e837bc111..f36b50f84d1 100755
Binary files a/packages/compositor-darwin-x64/libavcodec.dylib and b/packages/compositor-darwin-x64/libavcodec.dylib differ
diff --git a/packages/compositor-darwin-x64/remotion b/packages/compositor-darwin-x64/remotion
index 83b7c3e35d8..1d85f06fd7b 100755
Binary files a/packages/compositor-darwin-x64/remotion and b/packages/compositor-darwin-x64/remotion differ
diff --git a/packages/compositor-linux-arm64-gnu/ffmpeg b/packages/compositor-linux-arm64-gnu/ffmpeg
index 6acf8d228d6..63ead14b140 100755
Binary files a/packages/compositor-linux-arm64-gnu/ffmpeg and b/packages/compositor-linux-arm64-gnu/ffmpeg differ
diff --git a/packages/compositor-linux-arm64-gnu/ffprobe b/packages/compositor-linux-arm64-gnu/ffprobe
index a0f209b6003..b4fc1693d6c 100755
Binary files a/packages/compositor-linux-arm64-gnu/ffprobe and b/packages/compositor-linux-arm64-gnu/ffprobe differ
diff --git a/packages/compositor-linux-arm64-gnu/libavcodec.so b/packages/compositor-linux-arm64-gnu/libavcodec.so
index dfc374c0a16..ae42dfd2541 100755
Binary files a/packages/compositor-linux-arm64-gnu/libavcodec.so and b/packages/compositor-linux-arm64-gnu/libavcodec.so differ
diff --git a/packages/compositor-linux-arm64-gnu/libavformat.so b/packages/compositor-linux-arm64-gnu/libavformat.so
index b45cf03784b..4090a1dc15e 100755
Binary files a/packages/compositor-linux-arm64-gnu/libavformat.so and b/packages/compositor-linux-arm64-gnu/libavformat.so differ
diff --git a/packages/compositor-linux-arm64-gnu/libavutil.so b/packages/compositor-linux-arm64-gnu/libavutil.so
index e6769e52a20..d671d875cf5 100755
Binary files a/packages/compositor-linux-arm64-gnu/libavutil.so and b/packages/compositor-linux-arm64-gnu/libavutil.so differ
diff --git a/packages/compositor-linux-arm64-gnu/remotion b/packages/compositor-linux-arm64-gnu/remotion
index 3d1a88ad236..fd903f881e0 100755
Binary files a/packages/compositor-linux-arm64-gnu/remotion and b/packages/compositor-linux-arm64-gnu/remotion differ
diff --git a/packages/compositor-linux-arm64-musl/libavcodec.so b/packages/compositor-linux-arm64-musl/libavcodec.so
index a5c629e3c51..ae94bb83e44 100755
Binary files a/packages/compositor-linux-arm64-musl/libavcodec.so and b/packages/compositor-linux-arm64-musl/libavcodec.so differ
diff --git a/packages/compositor-linux-arm64-musl/remotion b/packages/compositor-linux-arm64-musl/remotion
index 10e01ccf932..7bbb10edbc1 100755
Binary files a/packages/compositor-linux-arm64-musl/remotion and b/packages/compositor-linux-arm64-musl/remotion differ
diff --git a/packages/compositor-linux-x64-gnu/ffmpeg b/packages/compositor-linux-x64-gnu/ffmpeg
index 530eb2dab3a..d66fbd53e1c 100755
Binary files a/packages/compositor-linux-x64-gnu/ffmpeg and b/packages/compositor-linux-x64-gnu/ffmpeg differ
diff --git a/packages/compositor-linux-x64-gnu/ffprobe b/packages/compositor-linux-x64-gnu/ffprobe
index 3608a909993..65ef48b9ae1 100755
Binary files a/packages/compositor-linux-x64-gnu/ffprobe and b/packages/compositor-linux-x64-gnu/ffprobe differ
diff --git a/packages/compositor-linux-x64-gnu/libavcodec.so b/packages/compositor-linux-x64-gnu/libavcodec.so
index bae16392aa4..f51285918c2 100755
Binary files a/packages/compositor-linux-x64-gnu/libavcodec.so and b/packages/compositor-linux-x64-gnu/libavcodec.so differ
diff --git a/packages/compositor-linux-x64-gnu/libavdevice.so b/packages/compositor-linux-x64-gnu/libavdevice.so
index 70a1874ce98..b15dd714594 100755
Binary files a/packages/compositor-linux-x64-gnu/libavdevice.so and b/packages/compositor-linux-x64-gnu/libavdevice.so differ
diff --git a/packages/compositor-linux-x64-gnu/libavfilter.so b/packages/compositor-linux-x64-gnu/libavfilter.so
index 129681c7091..d650c6a1f7c 100755
Binary files a/packages/compositor-linux-x64-gnu/libavfilter.so and b/packages/compositor-linux-x64-gnu/libavfilter.so differ
diff --git a/packages/compositor-linux-x64-gnu/libavformat.so b/packages/compositor-linux-x64-gnu/libavformat.so
index c2727e5e645..641d5e35463 100755
Binary files a/packages/compositor-linux-x64-gnu/libavformat.so and b/packages/compositor-linux-x64-gnu/libavformat.so differ
diff --git a/packages/compositor-linux-x64-gnu/libavutil.so b/packages/compositor-linux-x64-gnu/libavutil.so
index 733fad96c30..d7ccca4492a 100755
Binary files a/packages/compositor-linux-x64-gnu/libavutil.so and b/packages/compositor-linux-x64-gnu/libavutil.so differ
diff --git a/packages/compositor-linux-x64-gnu/libswresample.so b/packages/compositor-linux-x64-gnu/libswresample.so
index 2fd472796f2..f6343ad30f0 100755
Binary files a/packages/compositor-linux-x64-gnu/libswresample.so and b/packages/compositor-linux-x64-gnu/libswresample.so differ
diff --git a/packages/compositor-linux-x64-gnu/libswscale.so b/packages/compositor-linux-x64-gnu/libswscale.so
index 2bf83a9aec7..8f7f08950a7 100755
Binary files a/packages/compositor-linux-x64-gnu/libswscale.so and b/packages/compositor-linux-x64-gnu/libswscale.so differ
diff --git a/packages/compositor-linux-x64-gnu/remotion b/packages/compositor-linux-x64-gnu/remotion
index e204dc6010f..4a73ff4ea8d 100755
Binary files a/packages/compositor-linux-x64-gnu/remotion and b/packages/compositor-linux-x64-gnu/remotion differ
diff --git a/packages/compositor-linux-x64-musl/ffmpeg b/packages/compositor-linux-x64-musl/ffmpeg
index b590645b26b..410b1076563 100755
Binary files a/packages/compositor-linux-x64-musl/ffmpeg and b/packages/compositor-linux-x64-musl/ffmpeg differ
diff --git a/packages/compositor-linux-x64-musl/ffprobe b/packages/compositor-linux-x64-musl/ffprobe
index 2e0e385fe44..62816dbcebe 100755
Binary files a/packages/compositor-linux-x64-musl/ffprobe and b/packages/compositor-linux-x64-musl/ffprobe differ
diff --git a/packages/compositor-linux-x64-musl/libavcodec.so b/packages/compositor-linux-x64-musl/libavcodec.so
index 6d2d90eff4d..c4ca1db6ee6 100755
Binary files a/packages/compositor-linux-x64-musl/libavcodec.so and b/packages/compositor-linux-x64-musl/libavcodec.so differ
diff --git a/packages/compositor-linux-x64-musl/libavdevice.so b/packages/compositor-linux-x64-musl/libavdevice.so
index f05d5b8cda5..e2eff401a9c 100755
Binary files a/packages/compositor-linux-x64-musl/libavdevice.so and b/packages/compositor-linux-x64-musl/libavdevice.so differ
diff --git a/packages/compositor-linux-x64-musl/libavfilter.so b/packages/compositor-linux-x64-musl/libavfilter.so
index a3158b05d95..89c128f3549 100755
Binary files a/packages/compositor-linux-x64-musl/libavfilter.so and b/packages/compositor-linux-x64-musl/libavfilter.so differ
diff --git a/packages/compositor-linux-x64-musl/libavformat.so b/packages/compositor-linux-x64-musl/libavformat.so
index 4084523f7bb..2f58b6072b5 100755
Binary files a/packages/compositor-linux-x64-musl/libavformat.so and b/packages/compositor-linux-x64-musl/libavformat.so differ
diff --git a/packages/compositor-linux-x64-musl/libavutil.so b/packages/compositor-linux-x64-musl/libavutil.so
index a7245b6426e..5be7bbe498c 100755
Binary files a/packages/compositor-linux-x64-musl/libavutil.so and b/packages/compositor-linux-x64-musl/libavutil.so differ
diff --git a/packages/compositor-linux-x64-musl/libswresample.so b/packages/compositor-linux-x64-musl/libswresample.so
index 59678e114ad..9115742abc3 100755
Binary files a/packages/compositor-linux-x64-musl/libswresample.so and b/packages/compositor-linux-x64-musl/libswresample.so differ
diff --git a/packages/compositor-linux-x64-musl/libswscale.so b/packages/compositor-linux-x64-musl/libswscale.so
index 31d56475a02..f4310e59eb8 100755
Binary files a/packages/compositor-linux-x64-musl/libswscale.so and b/packages/compositor-linux-x64-musl/libswscale.so differ
diff --git a/packages/compositor-linux-x64-musl/remotion b/packages/compositor-linux-x64-musl/remotion
index 5fb28fbdf06..266b78ed847 100755
Binary files a/packages/compositor-linux-x64-musl/remotion and b/packages/compositor-linux-x64-musl/remotion differ
diff --git a/packages/compositor-win32-x64-msvc/avcodec-61.dll b/packages/compositor-win32-x64-msvc/avcodec-61.dll
index fde939f164f..d74aa3f2b4a 100755
Binary files a/packages/compositor-win32-x64-msvc/avcodec-61.dll and b/packages/compositor-win32-x64-msvc/avcodec-61.dll differ
diff --git a/packages/compositor-win32-x64-msvc/avdevice-61.dll b/packages/compositor-win32-x64-msvc/avdevice-61.dll
index 00b1978c460..a71e370f6c4 100755
Binary files a/packages/compositor-win32-x64-msvc/avdevice-61.dll and b/packages/compositor-win32-x64-msvc/avdevice-61.dll differ
diff --git a/packages/compositor-win32-x64-msvc/avfilter-10.dll b/packages/compositor-win32-x64-msvc/avfilter-10.dll
index 5ee208e5b4a..a33e5386746 100755
Binary files a/packages/compositor-win32-x64-msvc/avfilter-10.dll and b/packages/compositor-win32-x64-msvc/avfilter-10.dll differ
diff --git a/packages/compositor-win32-x64-msvc/avformat-61.dll b/packages/compositor-win32-x64-msvc/avformat-61.dll
index 3f0deadc515..f57e0f51df8 100755
Binary files a/packages/compositor-win32-x64-msvc/avformat-61.dll and b/packages/compositor-win32-x64-msvc/avformat-61.dll differ
diff --git a/packages/compositor-win32-x64-msvc/avutil-59.dll b/packages/compositor-win32-x64-msvc/avutil-59.dll
index 1f454e4e780..b63f939dcbf 100755
Binary files a/packages/compositor-win32-x64-msvc/avutil-59.dll and b/packages/compositor-win32-x64-msvc/avutil-59.dll differ
diff --git a/packages/compositor-win32-x64-msvc/ffmpeg.exe b/packages/compositor-win32-x64-msvc/ffmpeg.exe
index 6899098ccec..3d5e53d4654 100755
Binary files a/packages/compositor-win32-x64-msvc/ffmpeg.exe and b/packages/compositor-win32-x64-msvc/ffmpeg.exe differ
diff --git a/packages/compositor-win32-x64-msvc/ffprobe.exe b/packages/compositor-win32-x64-msvc/ffprobe.exe
index 6ecbd256426..110ee48cf79 100755
Binary files a/packages/compositor-win32-x64-msvc/ffprobe.exe and b/packages/compositor-win32-x64-msvc/ffprobe.exe differ
diff --git a/packages/compositor-win32-x64-msvc/libgcc_s_seh-1.dll b/packages/compositor-win32-x64-msvc/libgcc_s_seh-1.dll
index 03aaf66a5a9..d39dcd447e8 100644
Binary files a/packages/compositor-win32-x64-msvc/libgcc_s_seh-1.dll and b/packages/compositor-win32-x64-msvc/libgcc_s_seh-1.dll differ
diff --git a/packages/compositor-win32-x64-msvc/libstdc++-6.dll b/packages/compositor-win32-x64-msvc/libstdc++-6.dll
index 18dc6833610..921ea1b0a22 100644
Binary files a/packages/compositor-win32-x64-msvc/libstdc++-6.dll and b/packages/compositor-win32-x64-msvc/libstdc++-6.dll differ
diff --git a/packages/compositor-win32-x64-msvc/libvpx-1.dll b/packages/compositor-win32-x64-msvc/libvpx-1.dll
index c3ac173c941..9b4dcea7017 100755
Binary files a/packages/compositor-win32-x64-msvc/libvpx-1.dll and b/packages/compositor-win32-x64-msvc/libvpx-1.dll differ
diff --git a/packages/compositor-win32-x64-msvc/libwinpthread-1.dll b/packages/compositor-win32-x64-msvc/libwinpthread-1.dll
index 020da4f7ee6..52df15b9bb6 100644
Binary files a/packages/compositor-win32-x64-msvc/libwinpthread-1.dll and b/packages/compositor-win32-x64-msvc/libwinpthread-1.dll differ
diff --git a/packages/compositor-win32-x64-msvc/msvcr100.dll b/packages/compositor-win32-x64-msvc/msvcr100.dll
index 0cbdec9fe4c..0f4daa3e33f 100644
Binary files a/packages/compositor-win32-x64-msvc/msvcr100.dll and b/packages/compositor-win32-x64-msvc/msvcr100.dll differ
diff --git a/packages/compositor-win32-x64-msvc/remotion.exe b/packages/compositor-win32-x64-msvc/remotion.exe
index de512a739b8..9acad472a9f 100755
Binary files a/packages/compositor-win32-x64-msvc/remotion.exe and b/packages/compositor-win32-x64-msvc/remotion.exe differ
diff --git a/packages/compositor-win32-x64-msvc/swresample-5.dll b/packages/compositor-win32-x64-msvc/swresample-5.dll
index 90897e42326..902de128be3 100755
Binary files a/packages/compositor-win32-x64-msvc/swresample-5.dll and b/packages/compositor-win32-x64-msvc/swresample-5.dll differ
diff --git a/packages/compositor-win32-x64-msvc/swscale-8.dll b/packages/compositor-win32-x64-msvc/swscale-8.dll
index 5d89ee0adbc..b1a1d7aadd8 100755
Binary files a/packages/compositor-win32-x64-msvc/swscale-8.dll and b/packages/compositor-win32-x64-msvc/swscale-8.dll differ
diff --git a/packages/compositor-win32-x64-msvc/zlib1.dll b/packages/compositor-win32-x64-msvc/zlib1.dll
index bc7d15559d9..8a9be60e3a2 100644
Binary files a/packages/compositor-win32-x64-msvc/zlib1.dll and b/packages/compositor-win32-x64-msvc/zlib1.dll differ
diff --git a/packages/compositor/Cargo.lock b/packages/compositor/Cargo.lock
index 5b885c2e3e2..6da43410aab 100644
--- a/packages/compositor/Cargo.lock
+++ b/packages/compositor/Cargo.lock
@@ -167,7 +167,7 @@ dependencies = [
[[package]]
name = "ffmpeg-next"
version = "7.1.0"
-source = "git+https://github.com/remotion-dev/rust-ffmpeg?rev=873696322d49c429bee3ba018e8e0512a53f8ff3#873696322d49c429bee3ba018e8e0512a53f8ff3"
+source = "git+https://github.com/remotion-dev/rust-ffmpeg?rev=edc24627a8416b14e1259fe047ec2e9c8e9a2e18#edc24627a8416b14e1259fe047ec2e9c8e9a2e18"
dependencies = [
"bitflags 2.6.0",
"ffmpeg-sys-next",
@@ -177,7 +177,7 @@ dependencies = [
[[package]]
name = "ffmpeg-sys-next"
version = "7.1.0"
-source = "git+https://github.com/remotion-dev/rust-ffmpeg-sys?rev=73b853fd67aaa48c929db647c5e8045dca9b491a#73b853fd67aaa48c929db647c5e8045dca9b491a"
+source = "git+https://github.com/remotion-dev/rust-ffmpeg-sys?rev=b8edd4d52a09894d73888ea5f9281224c296c445#b8edd4d52a09894d73888ea5f9281224c296c445"
dependencies = [
"bindgen",
"cc",
diff --git a/packages/compositor/Cargo.toml b/packages/compositor/Cargo.toml
index 4c6dcbc2ec4..3f8d22c62de 100644
--- a/packages/compositor/Cargo.toml
+++ b/packages/compositor/Cargo.toml
@@ -15,9 +15,8 @@ lazy_static = "1.4"
rayon-core = "1.12.1"
sysinfo = "0.30.7"
mp4 = { git = "https://github.com/jonnyburger/mp4-rust", rev = "92ba375738cc2f05a4d754e1f968cf2e97d06641" }
-ffmpeg-next = { git = "https://github.com/remotion-dev/rust-ffmpeg", rev = "873696322d49c429bee3ba018e8e0512a53f8ff3" }
+ffmpeg-next = { git = "https://github.com/remotion-dev/rust-ffmpeg", rev = "edc24627a8416b14e1259fe047ec2e9c8e9a2e18" }
[[bin]]
name = "remotion"
path = "rust/main.rs"
-
diff --git a/packages/docs/docs/encoding.mdx b/packages/docs/docs/encoding.mdx
index d0bed6ccb88..e9f146626f3 100644
--- a/packages/docs/docs/encoding.mdx
+++ b/packages/docs/docs/encoding.mdx
@@ -35,7 +35,7 @@ Remotion supports 6 video codecs: `h264` (_default_), `h265`, `vp8`, `vp9`, `av1
Very good
-
No |
+ macOS, Linux, Windows (NVIDIA GPU) |
|
@@ -52,7 +52,7 @@ Remotion supports 6 video codecs: `h264` (_default_), `h265`, `vp8`, `vp9`, `av1
Very poor
|
- No |
+ macOS, Linux, Windows (NVIDIA GPU) |
| VP8 |
diff --git a/packages/docs/docs/hardware-acceleration.mdx b/packages/docs/docs/hardware-acceleration.mdx
index 830acaf55be..d8044a47d83 100644
--- a/packages/docs/docs/hardware-acceleration.mdx
+++ b/packages/docs/docs/hardware-acceleration.mdx
@@ -12,7 +12,8 @@ Besides rendering frames, encoding is one of the two steps required to create a
From Remotion v4.0.228, Remotion supports hardware-accelerated encoding in some cases.
Since encoding is platform- and codec-specific, only a few scenarios are supported at the moment.
-- Currently, only macOS is supported (Acceleration using VideoToolbox)
+- macOS: Acceleration using VideoToolbox
+- Linux and Windows: Acceleration using NVENC (requires an NVIDIA GPU)
- ProRes is supported from v4.0.228, H.264 and H.265 are supported from v4.0.236
## Enable hardware accelerated encoding
@@ -55,7 +56,7 @@ await renderMedia({
### In the CLI
-Use the [`--hardware-acceleration`](/docs/cli/render#--hardware-acceleration) option in the `npx remotion studio` command.
+Use the [`--hardware-acceleration`](/docs/cli/render#--hardware-acceleration) option in the `npx remotion render` command.
```bash
npx remotion render MyComp --codec prores --hardware-acceleration if-possible
@@ -79,6 +80,21 @@ Config.setHardwareAcceleration('if-possible');
These options are not supported in Remotion Lambda and Cloud Run, because those cloud services do not support hardware acceleration.
+## Prerequisites for NVENC (Linux/Windows)
+
+To use hardware-accelerated encoding on Linux and Windows, you need:
+
+- An NVIDIA GPU (GeForce, Quadro, or Tesla)
+- NVIDIA drivers installed (version 525+ recommended)
+- FFmpeg compiled with `h264_nvenc` or `hevc_nvenc` encoder support
+
+The FFmpeg binary bundled with Remotion does not include NVENC support. To use NVENC, point Remotion to an FFmpeg build that includes it using [`--binaries-directory`](/docs/cli/render#--binaries-directory) or [`binariesDirectory`](/docs/renderer/render-media#binariesdirectory).
+
+Set `--hardware-acceleration if-possible` and Remotion will use NVENC if the configured FFmpeg build supports it.
+
+Note that NVENC encoding is only available for H.264 and H.265 codecs. Other codecs will fall back to software encoding.
+
+
## Controlling quality using `--video-bitrate`
Note that the file size is significantly larger by default when using hardware acceleration, likely because less compression is applied.
@@ -95,6 +111,7 @@ If the render is using hardware acceleration, you will see a log message like th
```
Encoder: prores_videotoolbox, hardware accelerated: true
+Encoder: h264_nvenc, hardware accelerated: true
```
Don't rely on the exact wording of the log message to determine if hardware acceleration is being used.
diff --git a/packages/renderer/src/get-codec-name.ts b/packages/renderer/src/get-codec-name.ts
index 25cff3e6153..d21063993cb 100644
--- a/packages/renderer/src/get-codec-name.ts
+++ b/packages/renderer/src/get-codec-name.ts
@@ -98,6 +98,15 @@ export const getCodecName = ({
return {encoderName: 'h264_videotoolbox', hardwareAccelerated: true};
}
+ // NVENC for Linux/Windows
+ if (
+ preferredHwAcceleration &&
+ (process.platform === 'linux' || process.platform === 'win32') &&
+ !unsupportedQualityOption
+ ) {
+ return {encoderName: 'h264_nvenc', hardwareAccelerated: true};
+ }
+
warnAboutDisabledHardwareAcceleration();
return {encoderName: 'libx264', hardwareAccelerated: false};
@@ -112,6 +121,15 @@ export const getCodecName = ({
return {encoderName: 'hevc_videotoolbox', hardwareAccelerated: true};
}
+ // NVENC for Linux/Windows
+ if (
+ preferredHwAcceleration &&
+ (process.platform === 'linux' || process.platform === 'win32') &&
+ !unsupportedQualityOption
+ ) {
+ return {encoderName: 'hevc_nvenc', hardwareAccelerated: true};
+ }
+
warnAboutDisabledHardwareAcceleration();
return {encoderName: 'libx265', hardwareAccelerated: false};
diff --git a/packages/renderer/src/prespawn-ffmpeg.ts b/packages/renderer/src/prespawn-ffmpeg.ts
index 31b83610e82..987a4d1bd9a 100644
--- a/packages/renderer/src/prespawn-ffmpeg.ts
+++ b/packages/renderer/src/prespawn-ffmpeg.ts
@@ -18,6 +18,7 @@ import {
DEFAULT_PIXEL_FORMAT,
validateSelectedPixelFormatAndCodecCombination,
} from './pixel-format';
+import {resolveHardwareAcceleration} from './probe-encoder';
import {validateDimension, validateFps} from './validate';
import {validateEvenDimensionsWithCodec} from './validate-even-dimensions-with-codec';
@@ -89,6 +90,17 @@ export const prespawnFfmpeg = (options: PreStitcherOptions) => {
validateSelectedPixelFormatAndCodecCombination(pixelFormat, codec);
+ const resolvedHardwareAcceleration = resolveHardwareAcceleration({
+ codec,
+ hardwareAcceleration: options.hardwareAcceleration,
+ binariesDirectory: options.binariesDirectory,
+ indent: options.indent,
+ logLevel: options.logLevel,
+ crf: options.crf,
+ encodingMaxRate: options.encodingMaxRate,
+ encodingBufferSize: options.encodingBufferSize,
+ });
+
const ffmpegArgs = [
['-r', options.fps],
...[
@@ -111,7 +123,7 @@ export const prespawnFfmpeg = (options: PreStitcherOptions) => {
encodingMaxRate: options.encodingMaxRate,
encodingBufferSize: options.encodingBufferSize,
colorSpace: options.colorSpace,
- hardwareAcceleration: options.hardwareAcceleration,
+ hardwareAcceleration: resolvedHardwareAcceleration,
indent: options.indent,
logLevel: options.logLevel,
}),
diff --git a/packages/renderer/src/probe-encoder.ts b/packages/renderer/src/probe-encoder.ts
new file mode 100644
index 00000000000..9d3591c2135
--- /dev/null
+++ b/packages/renderer/src/probe-encoder.ts
@@ -0,0 +1,137 @@
+import {execFileSync} from 'node:child_process';
+import path from 'path';
+import type {Codec} from './codec';
+import {getExecutablePath} from './compositor/get-executable-path';
+import {getExplicitEnv} from './compositor/get-explicit-env';
+import {makeFileExecutableIfItIsNot} from './compositor/make-file-executable';
+import {getCodecName} from './get-codec-name';
+import type {LogLevel} from './log-level';
+import {Log} from './logger';
+import type {HardwareAccelerationOption} from './options/hardware-acceleration';
+
+/**
+ * Probes FFmpeg to check if a specific encoder is available.
+ * Returns true if the encoder is supported, false otherwise.
+ */
+export const probeEncoderAvailability = ({
+ encoderName,
+ binariesDirectory,
+ indent,
+ logLevel,
+}: {
+ encoderName: string;
+ binariesDirectory: string | null;
+ indent: boolean;
+ logLevel: LogLevel;
+}): boolean => {
+ try {
+ const executablePath = getExecutablePath({
+ type: 'ffmpeg',
+ indent,
+ logLevel,
+ binariesDirectory,
+ });
+ makeFileExecutableIfItIsNot(executablePath);
+
+ const cwd = path.dirname(executablePath);
+ const result = execFileSync(executablePath, ['-encoders'], {
+ encoding: 'utf-8',
+ env: getExplicitEnv(cwd),
+ stdio: ['pipe', 'pipe', 'pipe'],
+ timeout: 10000,
+ });
+
+ // FFmpeg -encoders output format: " V..... encoder_name"
+ const hasEncoder = result.includes(encoderName);
+ if (!hasEncoder) {
+ Log.verbose(
+ {indent, logLevel, tag: 'probeEncoderAvailability()'},
+ `Encoder "${encoderName}" not found in FFmpeg build. Falling back to software encoding.`,
+ );
+ }
+
+ return hasEncoder;
+ } catch (err) {
+ Log.verbose(
+ {indent, logLevel, tag: 'probeEncoderAvailability()'},
+ `Failed to probe FFmpeg for encoder "${encoderName}": ${err}. Falling back to software encoding.`,
+ );
+ return false;
+ }
+};
+
+/**
+ * Resolves the effective hardware acceleration setting by probing FFmpeg
+ * for encoder availability. If the preferred hw encoder is not available:
+ * - `if-possible` mode: falls back to software encoding
+ * - `required` mode: throws a clear error
+ */
+export const resolveHardwareAcceleration = ({
+ codec,
+ hardwareAcceleration,
+ binariesDirectory,
+ indent,
+ logLevel,
+ crf,
+ encodingMaxRate,
+ encodingBufferSize,
+}: {
+ codec: Codec;
+ hardwareAcceleration: HardwareAccelerationOption;
+ binariesDirectory: string | null;
+ indent: boolean;
+ logLevel: LogLevel;
+ crf: unknown;
+ encodingMaxRate: string | null;
+ encodingBufferSize: string | null;
+}): HardwareAccelerationOption => {
+ if (hardwareAcceleration === 'disable') {
+ return 'disable';
+ }
+
+ const preferred = getCodecName({
+ codec,
+ hardwareAcceleration,
+ crf,
+ encodingMaxRate,
+ encodingBufferSize,
+ logLevel,
+ indent,
+ });
+
+ // Audio codecs return null, no probing needed
+ if (preferred === null) {
+ return hardwareAcceleration;
+ }
+
+ // Only probe if getCodecName selected a hardware-accelerated encoder
+ if (!preferred.hardwareAccelerated) {
+ return hardwareAcceleration;
+ }
+
+ const encoderAvailable = probeEncoderAvailability({
+ encoderName: preferred.encoderName,
+ binariesDirectory,
+ indent,
+ logLevel,
+ });
+
+ if (encoderAvailable) {
+ return hardwareAcceleration;
+ }
+
+ // Encoder not available in FFmpeg
+ if (hardwareAcceleration === 'if-possible') {
+ Log.verbose(
+ {indent, logLevel, tag: 'resolveHardwareAcceleration()'},
+ `Hardware encoder "${preferred.encoderName}" not available. Falling back to software encoding.`,
+ );
+ return 'disable';
+ }
+
+ // hardwareAcceleration === 'required'
+ throw new Error(
+ `Hardware encoder "${preferred.encoderName}" is not available in your FFmpeg build. ` +
+ `Install an FFmpeg with ${preferred.encoderName} support, or use "if-possible" mode instead of "required".`,
+ );
+};
diff --git a/packages/renderer/src/stitch-frames-to-video.ts b/packages/renderer/src/stitch-frames-to-video.ts
index 8be53c40afe..5ad66c146da 100644
--- a/packages/renderer/src/stitch-frames-to-video.ts
+++ b/packages/renderer/src/stitch-frames-to-video.ts
@@ -35,12 +35,12 @@ import {
DEFAULT_PIXEL_FORMAT,
validateSelectedPixelFormatAndCodecCombination,
} from './pixel-format';
+import {resolveHardwareAcceleration} from './probe-encoder';
import {validateSelectedCodecAndProResCombination} from './prores-profile';
import {getShouldRenderAudio} from './render-has-audio';
import {validateDimension, validateFps} from './validate';
import {validateEvenDimensionsWithCodec} from './validate-even-dimensions-with-codec';
import {validateBitrate} from './validate-videobitrate';
-
type InternalStitchFramesToVideoOptions = {
audioBitrate: string | null;
videoBitrate: string | null;
@@ -347,6 +347,17 @@ const innerStitchFramesToVideo = async (
return Promise.resolve(file);
}
+ const resolvedHardwareAcceleration = resolveHardwareAcceleration({
+ codec,
+ hardwareAcceleration,
+ binariesDirectory,
+ indent: indent ?? false,
+ logLevel,
+ crf,
+ encodingMaxRate: maxRate,
+ encodingBufferSize: bufferSize,
+ });
+
const ffmpegArgs = [
...(preEncodedFileLocation
? [['-i', preEncodedFileLocation]]
@@ -376,7 +387,7 @@ const innerStitchFramesToVideo = async (
x264Preset,
gopSize,
colorSpace,
- hardwareAcceleration,
+ hardwareAcceleration: resolvedHardwareAcceleration,
indent,
logLevel,
}),
diff --git a/packages/renderer/src/test/get-codec-name.test.ts b/packages/renderer/src/test/get-codec-name.test.ts
new file mode 100644
index 00000000000..8129a25e623
--- /dev/null
+++ b/packages/renderer/src/test/get-codec-name.test.ts
@@ -0,0 +1,229 @@
+import {afterAll, afterEach, describe, expect, mock, test} from 'bun:test';
+import type {Codec} from '../codec';
+import type {HardwareAccelerationOption} from '../options/hardware-acceleration';
+
+// Mock remotion/version before importing the module under test
+mock.module('remotion/version', () => ({
+ VERSION: '4.0.0-test',
+}));
+
+const {getCodecName} = require('../get-codec-name');
+
+const originalPlatform = process.platform;
+
+const setPlatform = (platform: string) => {
+ Object.defineProperty(process, 'platform', {value: platform});
+};
+
+const restorePlatform = () => {
+ Object.defineProperty(process, 'platform', {value: originalPlatform});
+};
+
+const callGetCodecName = ({
+ codec,
+ hardwareAcceleration,
+ crf,
+ encodingMaxRate = null,
+ encodingBufferSize = null,
+}: {
+ codec: Codec;
+ hardwareAcceleration: HardwareAccelerationOption;
+ crf?: number;
+ encodingMaxRate?: string | null;
+ encodingBufferSize?: string | null;
+}) =>
+ getCodecName({
+ codec,
+ hardwareAcceleration,
+ crf: crf ?? null,
+ encodingMaxRate,
+ encodingBufferSize,
+ logLevel: 'warn',
+ indent: false,
+ });
+
+describe('getCodecName - macOS VideoToolbox (existing behavior)', () => {
+ afterEach(restorePlatform);
+
+ test('h264 + darwin + hwaccel:required + no CRF', () => {
+ setPlatform('darwin');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'h264_videotoolbox', hardwareAccelerated: true});
+ });
+
+ test('h265 + darwin + hwaccel:required + no CRF', () => {
+ setPlatform('darwin');
+ expect(
+ callGetCodecName({codec: 'h265', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'hevc_videotoolbox', hardwareAccelerated: true});
+ });
+
+ test('prores + darwin + hwaccel:required + no CRF', () => {
+ setPlatform('darwin');
+ expect(
+ callGetCodecName({codec: 'prores', hardwareAcceleration: 'required'}),
+ ).toEqual({
+ encoderName: 'prores_videotoolbox',
+ hardwareAccelerated: true,
+ });
+ });
+
+ test('h264 + darwin + hwaccel:required + crf=20 throws error', () => {
+ setPlatform('darwin');
+ expect(() =>
+ callGetCodecName({
+ codec: 'h264',
+ hardwareAcceleration: 'required',
+ crf: 20,
+ }),
+ ).toThrow(/hardware accelerated encoding/);
+ });
+
+ test('h264 + darwin + hwaccel:disable returns software encoder', () => {
+ setPlatform('darwin');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'disable'}),
+ ).toEqual({encoderName: 'libx264', hardwareAccelerated: false});
+ });
+});
+
+describe('getCodecName - NVENC on Linux', () => {
+ afterEach(restorePlatform);
+
+ test('h264 + linux + hwaccel:if-possible + no CRF', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'if-possible'}),
+ ).toEqual({encoderName: 'h264_nvenc', hardwareAccelerated: true});
+ });
+
+ test('h265 + linux + hwaccel:if-possible + no CRF', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h265', hardwareAcceleration: 'if-possible'}),
+ ).toEqual({encoderName: 'hevc_nvenc', hardwareAccelerated: true});
+ });
+
+ test('h264 + linux + hwaccel:required + no CRF', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'h264_nvenc', hardwareAccelerated: true});
+ });
+
+ test('h264 + linux + hwaccel:if-possible + crf=20 falls back to software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({
+ codec: 'h264',
+ hardwareAcceleration: 'if-possible',
+ crf: 20,
+ }),
+ ).toEqual({encoderName: 'libx264', hardwareAccelerated: false});
+ });
+});
+
+describe('getCodecName - NVENC on Windows', () => {
+ afterEach(restorePlatform);
+
+ test('h264 + win32 + hwaccel:if-possible + no CRF', () => {
+ setPlatform('win32');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'if-possible'}),
+ ).toEqual({encoderName: 'h264_nvenc', hardwareAccelerated: true});
+ });
+
+ test('h265 + win32 + hwaccel:if-possible + no CRF', () => {
+ setPlatform('win32');
+ expect(
+ callGetCodecName({codec: 'h265', hardwareAcceleration: 'if-possible'}),
+ ).toEqual({encoderName: 'hevc_nvenc', hardwareAccelerated: true});
+ });
+});
+
+describe('getCodecName - No hardware acceleration for unsupported codecs', () => {
+ afterEach(restorePlatform);
+
+ test('vp8 + linux + hwaccel:required returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'vp8', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'libvpx', hardwareAccelerated: false});
+ });
+
+ test('vp9 + linux + hwaccel:required returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'vp9', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'libvpx-vp9', hardwareAccelerated: false});
+ });
+
+ test('av1 + linux + hwaccel:required returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'av1', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'libaom-av1', hardwareAccelerated: false});
+ });
+
+ test('prores + linux + hwaccel:required returns software (no NVENC ProRes)', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'prores', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'prores_ks', hardwareAccelerated: false});
+ });
+
+ test('h264-mkv + linux + hwaccel:required returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h264-mkv', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'libx264', hardwareAccelerated: false});
+ });
+
+ test('h264-ts + linux + hwaccel:required returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h264-ts', hardwareAcceleration: 'required'}),
+ ).toEqual({encoderName: 'libx264', hardwareAccelerated: false});
+ });
+});
+
+describe('getCodecName - software fallback', () => {
+ afterEach(restorePlatform);
+
+ test('h264 + linux + hwaccel:disable returns software', () => {
+ setPlatform('linux');
+ expect(
+ callGetCodecName({codec: 'h264', hardwareAcceleration: 'disable'}),
+ ).toEqual({encoderName: 'libx264', hardwareAccelerated: false});
+ });
+
+ test('h265 + win32 + hwaccel:disable returns software', () => {
+ setPlatform('win32');
+ expect(
+ callGetCodecName({codec: 'h265', hardwareAcceleration: 'disable'}),
+ ).toEqual({encoderName: 'libx265', hardwareAccelerated: false});
+ });
+});
+
+describe('getCodecName - audio codecs return null', () => {
+ test('mp3 returns null', () => {
+ expect(
+ callGetCodecName({codec: 'mp3', hardwareAcceleration: 'disable'}),
+ ).toBeNull();
+ });
+
+ test('aac returns null', () => {
+ expect(
+ callGetCodecName({codec: 'aac', hardwareAcceleration: 'disable'}),
+ ).toBeNull();
+ });
+
+ test('wav returns null', () => {
+ expect(
+ callGetCodecName({codec: 'wav', hardwareAcceleration: 'disable'}),
+ ).toBeNull();
+ });
+});
+
+afterAll(restorePlatform);