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);