AlmaLinux

Install and Use FFmpeg on Rocky Linux 10 / AlmaLinux 10 / RHEL 10

FFmpeg is not in the Rocky Linux 10 base repositories, so a working install needs EPEL, CRB and RPM Fusion. On a clean 4 vCPU test box, RPM Fusion’s ffmpeg 7.1.5 (installed November 2026 on Rocky Linux 10.2) pulled 238 packages and 162 MB, and the whole transaction finished in 71 seconds. The same repo stack applies to AlmaLinux 10 and RHEL 10.

Original content from computingforgeeks.com - post 122379

We install FFmpeg first and then work through a tested ffmpeg commands cheat sheet. It covers probing, transcoding, trimming and joining, scaling, audio extraction and subtitles. It also shows what hardware acceleration does on a CPU-only host and which errors you will hit along the way. Every command and output below came from that box, including encode timings for four codecs across six encodes.

FFmpeg Cheat Sheet (Quick Reference)

Every row below is a complete command that ran on the Rocky Linux 10 test box against sample.mp4, a 20 second 1080p clip that Step 6 creates. In the lab each ffmpeg row also carried -hide_banner -loglevel error to keep the logs short. Those two flags change only what is printed, never the output file. Swap in your own file names and the commands work as written.

Probe a file

CommandWhat it does
ffprobe -hide_banner sample.mp4Container, duration, bitrate and every stream
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 sample.mp4Duration in seconds, nothing else
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt -of csv=p=0 sample.mp4Video codec, size, pixel format and frame rate as CSV
ffprobe -v error -show_entries format=format_name,duration,size,bit_rate -of json sample.mp4Format fields as JSON for scripts
ffprobe -v error -select_streams v:0 -skip_frame nokey -show_entries frame=pts_time -of csv=p=0 sample.mp4Keyframe timestamps (where a stream-copy cut can land)
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of csv=p=0 sample.mp4Exact frame count (decodes the whole stream)
ffmpeg -hide_banner -i sample.mp4 2>&1 | grep -E "Duration|Stream"Stream summary from ffmpeg itself; needs the stderr redirect

Transcode and remux

CommandWhat it does
ffmpeg -i sample.mp4 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k -movflags +faststart out-h264.mp4H.264 + AAC MP4, index at the front for web playback
ffmpeg -i sample.mp4 -c:v libx264 -crf 23 -preset veryfast -c:a aac -b:a 128k out-h264-veryfast.mp4Same quality target, 38% less wall time
ffmpeg -i sample.mp4 -c:v libx265 -crf 28 -preset medium -tag:v hvc1 -c:a copy out-h265.mp4HEVC with the hvc1 tag Apple players expect
ffmpeg -i sample.mp4 -c:v libsvtav1 -crf 35 -preset 8 -c:a libopus -b:a 96k out-av1.mkvAV1 (SVT-AV1) + Opus in Matroska
ffmpeg -i sample.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 -row-mt 1 -c:a libopus out-vp9.webmVP9 + Opus WebM, constant quality mode
ffmpeg -i sample.mp4 -c copy sample.mkvRemux MP4 to MKV without re-encoding
ffmpeg -i sample.mp4 -threads 2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k out-h264-t2.mp4Cap encoding at 2 threads on a shared host

Trim, split and concatenate

CommandWhat it does
ffmpeg -ss 00:00:05 -i sample.mp4 -t 4 -c copy cut-copy.mp4Instant cut, snaps to keyframes (came out 4.07 s)
ffmpeg -ss 00:00:05 -i sample.mp4 -t 4 -c:v libx264 -crf 20 -c:a aac cut-exact.mp4Frame-accurate cut by re-encoding (exactly 4.00 s)
ffmpeg -i sample.mp4 -ss 00:00:12 -to 00:00:15 -c:v libx264 -c:a aac cut-to.mp4Cut from one timestamp to another
printf "file '%s'\n" part1.mp4 part2.mp4 > list.txtBuild the list file for the concat demuxer
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4Join same-codec files without re-encoding
ffmpeg -i sample.mp4 -c copy -f segment -segment_time 5 -reset_timestamps 1 seg_%03d.mp4Split at the first keyframe after each 5 s mark (8.3 s pieces here)

Scale, crop and grab frames

CommandWhat it does
ffmpeg -i sample.mp4 -vf scale=1280:-2 -c:a copy out-720p.mp4Resize to 1280 wide, height kept even (1280×720)
ffmpeg -i sample.mp4 -vf "scale=-2:480,fps=24" -c:a copy out-480p.mp4480p at 24 fps (854×480)
ffmpeg -i sample.mp4 -vf "crop=1080:1080" -c:a copy square.mp4Centre crop to a square
ffmpeg -ss 00:00:07 -i sample.mp4 -frames:v 1 -q:v 2 thumb.jpgOne JPEG thumbnail at 7 s
ffmpeg -i sample.mp4 -vf fps=1/5 frame_%02d.pngOne PNG every 5 seconds
ffmpeg -ss 2 -t 3 -i sample.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" clip.gif3 second GIF with a generated palette

Extract and fix audio

CommandWhat it does
ffmpeg -i sample.mp4 -vn -c:a copy audio.m4aPull the AAC track out untouched
ffmpeg -i sample.mp4 -vn -c:a libmp3lame -q:a 2 audio.mp3Audio to VBR MP3
ffmpeg -i sample.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le speech.wav16 kHz mono WAV for speech-to-text tools
ffmpeg -i sample.mp4 -an -c:v copy silent.mp4Drop the audio, keep the video bitstream
ffmpeg -i sample.mp4 -af volumedetect -vn -f null - 2>&1 | grep -E "mean_volume|max_volume"Measure loudness without writing a file
ffmpeg -i sample.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:v copy normalized.mp4EBU R128 loudness normalisation, video copied
ffmpeg -i silent.mp4 -i audio.mp3 -map 0:v -map 1:a -c:v copy -c:a aac -shortest dubbed.mp4Replace the audio track with another file

Subtitles

CommandWhat it does
ffmpeg -i sample.mp4 -i subs.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:0 language=eng soft-subs.mp4Soft subtitles in MP4 (SRT converted to mov_text)
ffmpeg -i sample.mp4 -i subs.srt -map 0 -map 1 -c copy soft-subs.mkvSoft subtitles in MKV (SRT kept as subrip)
ffmpeg -i sample.mp4 -vf subtitles=subs.srt -c:a copy burned.mp4Burn subtitles into the picture (libass)
ffmpeg -i soft-subs.mkv -map 0:s:0 extracted.srtExtract the first subtitle stream to SRT

Hardware acceleration checks

CommandWhat it does
ffmpeg -hide_banner -hwaccelsMethods compiled in (not what the host can use)
ffmpeg -hide_banner -encoders | grep -E "nvenc|vaapi|qsv|amf|vulkan"Hardware encoders in this build
ls -l /dev/driRender nodes for VAAPI and QSV; missing on a plain VM
ffmpeg -hwaccel auto -i sample.mp4 -t 2 -f null -Tries each method, falls back to software, exits 0
ffmpeg -benchmark -i sample.mp4 -f null - 2>&1 | tail -2CPU decode baseline (2.6 s for the 20 s clip)

Overwrites, optional streams and batches

CommandWhat it does
ffmpeg -y -i sample.mp4 -c copy sample.mkvOverwrite the output without asking
ffmpeg -n -i sample.mp4 -c copy sample.mkvNever overwrite; skips and still exits 0
ffmpeg -i silent.mp4 -map 0:v -map 0:a? -c copy maybe-audio.mp4Map audio only if the input has any
for f in part*.mp4; do ffmpeg -n -i "$f" -c:v libvpx-vp9 -crf 34 -b:v 0 -c:a libopus "webm/${f%.mp4}.webm"; doneBatch convert a folder to WebM (create webm/ first)

Below the tables we install FFmpeg and explain the commands that need it. That includes why a stream-copy cut came out 67 ms long, why -n is a trap in scripts, and what each hardware path printed on a host with no GPU.

Prerequisites

You need a Rocky Linux 10, AlmaLinux 10 or RHEL 10 host with sudo and outbound HTTPS to the EPEL and RPM Fusion mirrors. Transcoding is CPU-bound, so the timings in Step 7 scale with core count. Treat our numbers as a floor for planning, not a target.

  • Test box: Rocky Linux 10.2 (kernel 6.12), 4 vCPU on an Intel Core i7-1165G7 host, 4 GB RAM allocated (3.6 GB visible to free), SELinux enforcing. AlmaLinux 10 uses the same commands; RHEL 10 differs only in how EPEL and CRB are enabled (see Step 2).
  • Disk: the FFmpeg transaction installs 238 packages, a 162 MB download that expands to 578 MB on disk, so leave at least 1 GB free on / before you start.
  • A server to run it on: DigitalOcean gives new accounts $200 in credits, enough for a Rocky 10 droplet for several months of FFmpeg work.

Step 1: Confirm the base repos do not ship FFmpeg

Rocky Linux 10 ships BaseOS, AppStream and Extras, and none of them carries FFmpeg. Red Hat leaves it out by policy because several codecs it links against (x264, x265) are patent-encumbered in some jurisdictions. A search on the fresh box shows it.

dnf search ffmpeg

After the metadata refresh there is no match at all, which confirms the repos have to be added by hand:

Rocky Linux 10 - BaseOS                         6.8 MB/s |  42 MB     00:06
Rocky Linux 10 - AppStream                      1.2 MB/s | 2.9 MB     00:02
Rocky Linux 10 - Extras                         5.2 kB/s | 6.1 kB     00:01
No matches found.

FFmpeg comes from three other repositories instead. EPEL supplies codec libraries such as SVT-AV1 and a cut-down ffmpeg-free, CRB supplies the devel libraries EPEL builds against, and RPM Fusion supplies the full ffmpeg build with x264 and x265.

Step 2: Enable EPEL and CRB

Rocky ships epel-release in its Extras repo, so one install command adds EPEL.

sudo dnf install -y epel-release

Its scriptlet prints a reminder that most EPEL packages need CRB, which is the next command.

Many EPEL packages require the CodeReady Builder (CRB) repository.
It is recommended that you run /usr/bin/crb enable to enable the CRB repository.

Installed:
  epel-release-10-7.el10_1.noarch

Complete!

Enable CRB with the helper that epel-release installs. EPEL packages that link against CRB libraries do not resolve without it.

sudo /usr/bin/crb enable

A one-line confirmation names the repo.

Enabling CRB repo
CRB repo is enabled and named: crb

Our EPEL setup guide for Rocky Linux and AlmaLinux covers the RHEL variant. On RHEL 10, epel-release is not in any Red Hat repo, so the EPEL getting started page has you enable CodeReady Builder through subscription-manager and install the epel-release-latest-10 RPM from the Fedora mirror instead. We ran the Rocky commands above, not the RHEL ones.

Step 3: Add RPM Fusion free and nonfree

RPM Fusion is the community repository that packages what Red Hat and Fedora exclude. Its EL 10 release RPMs install directly from the mirror URL. Add the free repo first, since it carries ffmpeg itself.

sudo dnf install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-10.noarch.rpm

Nonfree is optional for the CLI, but it holds the packages with restrictive licences, so most media hosts add it at the same time.

sudo dnf install -y https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-10.noarch.rpm

On an EL 9 host the same URLs end in -release-9.noarch.rpm, and the RPM Fusion setup guide lists both. Before installing anything, confirm the four repos are enabled.

dnf repolist enabled | grep -iE 'crb|epel|rpmfusion'

Four lines means the repo stack is complete. If one is missing, the install in Step 4 either fails or quietly pulls the wrong build:

crb                          Rocky Linux 10 - CRB
epel                         Extra Packages for Enterprise Linux 10 - x86_64
rpmfusion-free-updates       RPM Fusion for EL 10 - Free - Updates
rpmfusion-nonfree-updates    RPM Fusion for EL 10 - Nonfree - Updates

With CRB, EPEL and both RPM Fusion repos enabled, DNF can resolve the full FFmpeg build and every codec library it links against.

Step 4: Install FFmpeg

Install the binary and the development headers. The ffmpeg-devel package is not needed for the CLI, but anything that compiles against libavcodec (Python bindings, OBS plugins) needs it, and adding it now costs one extra package.

sudo dnf install -y ffmpeg ffmpeg-devel

Check the transaction summary first. Both ffmpeg packages must come from rpmfusion-free-updates, not from EPEL.

Installing:
 ffmpeg                                    x86_64  7.1.5-1.el10                      rpmfusion-free-updates  1.9 M
 ffmpeg-devel                              x86_64  7.1.5-1.el10                      rpmfusion-free-updates  881 k
...
Install  238 Packages

Total download size: 162 M

Measured with time, the install took 1m11s of wall clock. Almost all of it was download, since user CPU was only 7.5 s. Among the 238 packages are x264-libs 0.164 and x265-libs 4.1 from RPM Fusion and svt-av1-libs 2.3.0 from EPEL. EPEL also carries ffmpeg-free 7.1.2, a different build with a different encoder set; the Troubleshooting section shows what changes if DNF picks it.

Step 5: Verify the build and its encoders

Start with the version banner. It names the build, and the long configuration: line lists every library compiled in. The RPM Fusion build on the test box carries --enable-libx264 --enable-libx265 --enable-libsvtav1 --enable-libass --enable-nvenc among 84 --enable options, and its compiler flags target -march=x86-64-v3, the same baseline RHEL 10 itself requires.

ffmpeg -version

Then filter the encoder list for the four that most jobs use. ffmpeg -encoders prints the list on stdout, so a plain pipe to grep works here.

ffmpeg -hide_banner -encoders | grep -E "libx264|libx265|aac |libmp3lame"

Both checks, plus the codec library versions behind them, came out like this on the test box.

ffmpeg 7.1.5 version, libx264 and libx265 encoders and codec library versions on Rocky Linux 10.2

Read the six-character column on the left position by position. V or A marks video or audio, and the trailing D means the encoder supports direct rendering method 1, an internal buffer feature. It does not mean hardware decoding. The full build lists 232 encoders in total.

Step 6: Create a test clip and probe it

A synthetic clip makes every command in this guide reproducible without downloading media. The lavfi inputs generate a 20 second 1080p30 test pattern and a 440 Hz tone, which the command encodes to H.264 and AAC.

ffmpeg -hide_banner -f lavfi -i testsrc2=duration=20:size=1920x1080:rate=30 -f lavfi -i "sine=frequency=440:duration=20" -c:v libx264 -c:a aac -pix_fmt yuv420p sample.mp4

You get a 15 MB file at 6.2 Mb/s. For scripts, ffprobe with -v error and an explicit -show_entries list is the right tool, because it prints only the requested fields on stdout. The duration query returns 20.000000 and the stream query returns h264,1920,1080,yuv420p,30/1, both ready for a shell variable.

ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt -of csv=p=0 sample.mp4

Keyframe positions matter more than they look. The query listed keyframes at 0, 8.33 and 16.67 seconds, which is the libx264 default of one keyframe every 250 frames at 30 fps, and those three timestamps decide where a stream-copy cut in Step 8 can start:

ffprobe printing duration, video stream fields as CSV, format JSON and keyframe timestamps

One trap catches almost everyone who greps ffmpeg output. ffmpeg writes its stream information to stderr, so ffmpeg -i sample.mp4 | grep Stream filters nothing: the whole banner still reaches the terminal, followed by At least one output file must be specified and exit code 1. Redirect stderr first and the filter works:

ffmpeg -hide_banner -i sample.mp4 2>&1 | grep -E "Duration|Stream"

Only the three matching lines come back, which is what a script parsing the duration or codec wants:

  Duration: 00:00:20.00, start: 0.000000, bitrate: 6226 kb/s
  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 6148 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 69 kb/s (default)

The same rule applies to every ffmpeg log line you filter, including the volumedetect output in Step 10: send stderr into the pipe, or the filter sees nothing.

Step 7: Transcode and measure the encoder cost

Codec choice is mostly a CPU budget decision, so we timed each transcode row in the cheat sheet on the same 20 second 1080p clip with the bash time keyword. Each encoder ran once, one after another, on an otherwise idle 4 vCPU box.

Encoder and settingsWall timeUser CPUOutput size
Remux, -c copy to MKV0.18 s0.10 s15,192 KB
libx264, CRF 23, veryfast9.8 s20.2 s12,565 KB
libx264, CRF 23, medium15.8 s37.0 s14,396 KB
libx264, CRF 23, medium, -threads 221.2 s32.8 s14,381 KB
libx265, CRF 28, medium38.7 s84.0 s10,727 KB
libsvtav1, CRF 35, preset 840.8 s65.3 s12,640 KB
libvpx-vp9, CRF 32, -row-mt 1156.3 s274.8 s16,164 KB

libx264 at the medium preset encodes this clip at 1.26x real time on 4 vCPU, so a one hour 1080p file needs roughly 48 minutes. HEVC took 2.4 times as long as H.264 and AV1 at preset 8 took 2.6 times. VP9 is the outlier at 156 seconds, ten times the H.264 cost, even with row multithreading on. The remux row is the reason to try -c copy before anything else: when only the container has to change, the job takes milliseconds and the bitstream is untouched.

ffmpeg H.264, VP9 and remux encode times and output sizes on a 4 vCPU Rocky Linux 10 VM

Read our size column with care. testsrc2 is a moving synthetic pattern with sharp edges, which is hard to compress, so VP9 at CRF 32 came out larger than the source. Treat the timings as the result and re-check sizes against your own footage. Two flags in the H.264 and HEVC rows are worth keeping. -movflags +faststart moves the MP4 index in front of the media data. A trace probe of out-h264.mp4 shows the moov atom before mdat, and that lets browsers start playback before the download ends. -tag:v hvc1 writes the HEVC sample entry as hvc1 (ffprobe reports hevc,hvc1), which Apple players expect.

Step 8: Trim, split and join clips

A stream-copy cut cannot start between keyframes, because the frames after the cut point reference a keyframe that would no longer exist. Asking for 4 seconds from the 5 second mark with -c copy returned a 4.067 s file, while the re-encoded version came out at exactly 4.000 s. The copy keeps the whole group of pictures from the keyframe at 0 s and the MP4 muxer hides the frames before 5 s behind an edit list, so a player that ignores edit lists shows them.

ffmpeg -ss 00:00:05 -i sample.mp4 -t 4 -c copy cut-copy.mp4
ffmpeg -ss 00:00:05 -i sample.mp4 -t 4 -c:v libx264 -crf 20 -c:a aac cut-exact.mp4

We pick between the two by what happens to the clip next. Putting -ss before -i seeks the input quickly, and both cuts above use that order.

  • Stream copy (4.067 s): instant and lossless, so it suits trimming a recording to roughly the right length before archiving it.
  • Re-encode (4.000 s): costs a full encode but lands on the exact frame, which matters for clips you join or sync to audio later.

Joining files uses the concat demuxer with a list file. The list was built with printf so the quoting is correct, and the two 10 second halves joined back into a 20.09 s file without re-encoding.

printf "file '%s'\n" part1.mp4 part2.mp4 > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4

Keep -safe 0 in the command. Without it, a list with absolute paths fails before any data is read, as the last command in the capture shows:

ffmpeg trim with stream copy versus re-encode durations and concat demuxer Unsafe file name error

Concat with -c copy requires every part to share codec, resolution and timebase. Parts cut from one source, like these, always do. Files from different cameras or phones usually do not, so re-encode each one to the same settings first, then concatenate.

Step 9: Scale, crop and grab frames

Use -2, not -1, for the dimension ffmpeg should calculate. Both keep the aspect ratio, but -2 also rounds to an even number, and libx264 with 4:2:0 chroma refuses odd sizes. Scaling the 1080p clip with scale=-1:361 produced 642×361 and stopped at the encoder.

[libx264 @ 0x55e76880b640] height not divisible by 2 (642x361)
[vost#0:0/libx264 @ 0x55e76886f080] Error while opening encoder - maybe incorrect parameters such as bit_rate, rate, width or height.

With -2 the same filter returned 1280×720 for scale=1280:-2 and 854×480 at 24/1 fps for scale=-2:480,fps=24. A filter chain separates filters with commas. A centre crop to 1080×1080 needs no offsets, because crop centres by default.

ffmpeg scale to 720p and 480p and the libx264 height not divisible by 2 error

For stills, -frames:v 1 writes one image and -vf fps=1/5 writes one every 5 seconds, which gave four PNGs for the 20 second clip. The GIF row runs palettegen and paletteuse in one pass, so the 3 second, 480 px wide GIF came out at 451 KB without the banding of the default 256-colour palette.

Step 10: Extract and replace audio

-vn drops video and -an drops audio. The rest is a choice between copying the existing track and encoding a new one. Copying AAC into audio.m4a kept it at aac,44100,1 with no quality loss. Speech-to-text tools such as Whisper want 16 kHz mono PCM, and the -ac 1 -ar 16000 -c:a pcm_s16le row returned exactly pcm_s16le,16000,1:

ffmpeg -i sample.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le speech.wav

Measure before you normalise. The volumedetect filter with -f null - reads the whole track and writes nothing. The test tone measured a mean of -21.1 dB and a peak of -12.4 dB. As with probing, those lines arrive on stderr, so the cheat sheet row redirects with 2>&1 before the grep. The loudnorm row then brings a track to the EBU R128 target of -16 LUFS while copying the video stream unchanged.

Replacing a soundtrack needs explicit maps, because by default ffmpeg picks one stream of each type from all inputs. -map 0:v -map 1:a takes video from the first file and audio from the second, and -shortest trims the output to the shorter of the two. The dubbed file probed as h264 plus aac. For MP3 bitrate options and batch extraction, the video to MP3 conversion guide goes further.

Step 11: Add, burn and extract subtitles

Soft subtitles are a separate stream the player can switch off, while burned subtitles are drawn into the picture and cannot be removed. The test file used a two-cue SRT created with printf.

printf '1\n00:00:01,000 --> 00:00:04,000\nTested on Rocky Linux 10\n\n2\n00:00:05,000 --> 00:00:08,000\nffmpeg cheat sheet demo\n' > subs.srt

MP4 cannot store SRT as is. Copying it with a plain -c copy fails with Could not find tag for codec subrip in stream #2, so the MP4 command adds -c:s mov_text to convert just the subtitle stream. Matroska stores subrip natively, so the MKV version needs no conversion.

ffmpeg audio extraction to m4a and 16 kHz WAV, soft subtitles as mov_text and the subrip MP4 error

Burning uses the subtitles filter, which needs libass and at least one font. The RPM Fusion build has libass 0.17.3, and after the FFmpeg install the box had 37 fonts registered with fontconfig, so -vf subtitles=subs.srt worked on the first run. Extracting the stream back from the MKV produced cues shifted by 23 ms (00:00:01,023 instead of 00:00:01,000), so check timings after a round trip if frame accuracy matters.

Step 12: Check hardware acceleration on a CPU-only host

ffmpeg -hwaccels lists what the binary was compiled with, not what the host can run. The RPM Fusion build lists seven methods (vdpau, cuda, vaapi, qsv, drm, opencl, vulkan) and 21 hardware encoders across NVENC, VAAPI, QSV, AMF and Vulkan. On the test VM, which has no GPU passed through, /dev/dri does not exist, and every one of those paths fails differently.

ffmpeg hwaccels list, missing /dev/dri and Cannot load libcuda.so.1 on a CPU-only Rocky Linux VM

Each failure points at a missing driver or device, never at a missing ffmpeg feature.

  • -hwaccel cuda and h264_nvenc: Cannot load libcuda.so.1. The NVIDIA driver library is absent; the encoder exists in the build.
  • h264_vaapi: No VA display found for device /dev/dri/renderD128, then exit code 234 before any input is read.
  • h264_qsv: Error creating a MFX session: -9. No Intel GPU is visible to the guest.
  • -hwaccel auto: probes CUDA, then render nodes renderD128 upward, gives up, decodes in software at 7.38x and exits 0.

Remember the last one. -hwaccel auto never fails a job, so a pipeline that “uses the GPU” can run on the CPU for months without an error. Only -v verbose output or the speed figure gives it away. Pin an explicit method in production so a missing driver fails loudly. The software decode baseline from -benchmark was 2.6 s real time for the 20 second clip. For choosing hardware that does accelerate this, the GPU guide for Jellyfin and Plex transcoding compares Quick Sync and Arc.

Static build fallback

When system repos are off limits (a locked-down RHEL host, or a need to pin one upstream release), John Van Sickle’s static build runs on any x86-64 Linux without dependencies. The release offered on that page in November 2026 is still 7.0.2, older than the 7.1.5 RPM Fusion package. The minimal Rocky 10 cloud image does not include wget, so install it first.

sudo dnf install -y wget

Then download and extract the release tarball into a directory you own, so nothing lands in the system paths yet:

mkdir -p ~/ffmpeg-static
cd ~/ffmpeg-static
wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
tar -xf ffmpeg-release-amd64-static.tar.xz

Copy both binaries into /usr/local/bin. On the test box that directory comes before /usr/bin in $PATH, so the static binary takes over the plain ffmpeg command even with the RPM installed.

sudo install -m755 ffmpeg-*-amd64-static/ffmpeg /usr/local/bin/
sudo install -m755 ffmpeg-*-amd64-static/ffprobe /usr/local/bin/

Check the version string for the static origin. This capture is from the April 2026 run of this guide on Rocky Linux 10.1:

/usr/local/bin/ffmpeg -version | head -3

Expect output like this, with the build date and the Debian toolchain that compiled it:

ffmpeg version 7.0.2-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2024 the FFmpeg developers
built with gcc 8 (Debian 8.3.0-6)
configuration: --enable-gpl --enable-version3 --enable-static --disable-debug ...

John Van Sickle’s build has no NVENC, VAAPI or QSV support, because those need driver libraries that a static binary cannot assume. If the host has a GPU, the RPM Fusion package is the better choice.

Troubleshooting common FFmpeg errors

We reproduced each error below on the test box. ffmpeg’s exit codes are not consistent across failure types, which is why the first one matters for anyone scripting it.

“File ‘sample.mkv’ already exists. Overwrite? [y/N] Not overwriting – exiting”

When stdin is not a terminal (cron, CI, or stdin redirected from /dev/null), ffmpeg cannot ask, so it answers no. It prints the message and exits with code 0, as does -n when it skips an existing file. A script that checks $? records a success for a file that was never written.

ffmpeg Not overwriting exiting with exit code 0, missing input file and moov atom not found errors

Pass -y or -n explicitly in every script, and when skipping matters, test for the output file yourself rather than trusting the exit code. The same capture shows the real failures exiting non-zero: a missing input returned 254 and a text file renamed to .mp4 returned 183 with moov atom not found.

“Unknown encoder ‘libx264′”

You have the EPEL build, ffmpeg-free, installed instead of RPM Fusion’s ffmpeg. Swapping to it on the test box and running the H.264 command reproduced the error with exit code 8. The two builds carry different encoders: ffmpeg-free 7.1.2 has libopenh264 and libfdk_aac but no x264 or x265, while RPM Fusion has x264 and x265 but no libfdk_aac. Swap back in one transaction:

sudo dnf swap -y ffmpeg-free ffmpeg --allowerasing

Our swap away from RPM Fusion removed ffmpeg-devel as a dependent package and the swap back does not reinstall it, so run sudo dnf install -y ffmpeg-devel afterwards if anything compiles against libavcodec.

“Unknown encoder ‘libfdk_aac'”

Older guides use -c:a libfdk_aac, and the RPM Fusion build answers with Error opening output files: Encoder not found and exit code 8. Use the native encoder, -c:a aac -b:a 128k, which every row in the cheat sheet uses.

“height not divisible by 2 (642×361)”

libx264 needs even dimensions for 4:2:0 output. Replace -1 with -2 in the scale filter so the calculated side is rounded to an even value (Step 9).

“Could not find tag for codec subrip in stream #2, codec not currently supported in container”

An SRT stream was copied into MP4, which has no subrip mapping. Add -c:s mov_text, or write to .mkv instead. The failed run ended with Conversion failed! and exit code 234.

“Unsafe file name” from the concat demuxer

Your list file contains absolute paths or names with characters the demuxer treats as unsafe. Add -safe 0 before -i list.txt. Without it the run ended with Operation not permitted and exit code 255.

“Stream map ” matches no streams”

A -map 0:a pointed at a file with no audio track. ffmpeg suggests the fix in the next line: a trailing question mark, -map 0:a?, makes the map optional, and the same command then wrote a video-only file.

“GPG check FAILED” on RPM Fusion packages

RPM Fusion’s release RPMs drop their keys in /etc/pki/rpm-gpg/ and DNF imports them on first use. If that import was skipped, import both keys by hand and retry the install.

sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-el-10
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-el-10

On the test box both keys then showed up in rpm -q gpg-pubkey as the RPM Fusion free and nonfree keys for EL (10), so a retried install can verify the packages.

SELinux denies writes under /var/www

Rocky Linux 10 runs SELinux in enforcing mode, and a web service that transcodes uploads cannot write to a directory labelled var_t. Label the media directory instead of disabling SELinux.

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/media(/.*)?"
sudo restorecon -Rv /var/www/media

On the test box restorecon reported the relabel from var_t to httpd_sys_rw_content_t, and the rule persists across relabels because it lives in the policy, not on the inode.

Running FFmpeg on a shared host: what thread caps cost

Left alone, libx264 on the 4 vCPU box defaults to 6 encoding threads (threads=6 in the x264 options line when the sample clip was made) and used 37.0 s of CPU in 15.8 s of wall time, an average of 2.3 cores busy. Capping it with -threads 2 stretched the job to 21.2 s, 34% longer, while total CPU fell to 32.8 s and the output size stayed within 0.1%. On a host that also serves a database or web stack, that trade is usually right: the encode finishes a few seconds later and the other services keep their cores.

Preset is the bigger lever in our runs. Moving libx264 from medium to veryfast cut wall time by 38% and CPU time by 45%, at the cost of slightly less efficient compression on real footage. For background jobs that outlive an SSH session, run ffmpeg under tmux or a systemd unit and send stderr to a log file, since that is where its progress and errors go. The Rocky Linux 10 post-install tips cover the swap and SELinux baselines a media host needs, and for Fedora desktops the FFmpeg on Fedora guide compares the same RPM Fusion and ffmpeg-free split there.

Keep reading

Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 AlmaLinux Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 Customize KDE Plasma Desktop with Themes|Modules|Extensions AlmaLinux Customize KDE Plasma Desktop with Themes|Modules|Extensions Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) Cheat Sheets Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) Install Zabbix 8.0 LTS on Rocky Linux 10 / AlmaLinux 10 AlmaLinux Install Zabbix 8.0 LTS on Rocky Linux 10 / AlmaLinux 10 Install K3s on Rocky Linux 10 (Kubernetes Cluster Setup) Containers Install K3s on Rocky Linux 10 (Kubernetes Cluster Setup) Install and Use sshfs on Rocky 10 / Debian 13 / Ubuntu 24.04 AlmaLinux Install and Use sshfs on Rocky 10 / Debian 13 / Ubuntu 24.04

Leave a Comment

Press ESC to close