Use this Bash script to convert common video files recursively to HEVC with FFmpeg’s libx265 encoder. It preserves every source file and renames an output into place only after FFmpeg exits successfully and ffprobe reports HEVC for the first selected video stream. That is a codec check, not a full decode, duration comparison, or stream-by-stream validation.

Install and inspect FFmpeg

On Ubuntu or Debian:

sudo apt install ffmpeg screen

On Manjaro, use the repository package available for your installed drivers:

pamac install ffmpeg screen

Confirm that this build includes x265 and inspect available hardware encoders:

ffmpeg -hide_banner -encoders | grep -E 'libx265|hevc_.*api|hevc_.*sv|hevc_nvenc'

The upstream FFmpeg codec documentation describes encoder-specific options. Test one representative file before starting a library-wide job.

A filename-safe conversion script

Create ~/convert-video, make it executable only by your user, and open it in an editor:

touch ~/convert-video
chmod 700 ~/convert-video
nano ~/convert-video

Use this script:

#!/usr/bin/env bash
set -u

if (( $# != 1 )); then
    printf 'Usage: %s DIRECTORY\\n' "$0" >&2
    exit 2
fi

root=$1
if [[ ! -d $root ]]; then
    printf 'Not a directory: %s\\n' "$root" >&2
    exit 2
fi

find "$root" -type f \\
    \( -iname '*.mkv' -o -iname '*.mp4' -o -iname '*.mpg' \\
       -o -iname '*.mpeg' -o -iname '*.mov' -o -iname '*.avi' \) \\
    ! -iname '* - HEVC.mkv' -print0 |
while IFS= read -r -d '' input; do
    output="${input%.*} - HEVC.mkv"
    partial="${output%.mkv}.part.mkv"

    if [[ -e $output ]]; then
        printf 'Skipping existing output: %s\\n' "$output"
        continue
    fi

    if [[ -e $partial ]]; then
        printf 'Refusing existing partial file: %s\\n' "$partial" >&2
        continue
    fi
    printf 'Converting: %s\\n' "$input"

    if nice -n 15 ffmpeg -nostdin -hide_banner -i "$input" \\
        -map 0:v:0 -map '0:a?' -map '0:s?' \\
        -c:v libx265 -preset medium -crf 26 \\
        -c:a copy -c:s copy -f matroska "$partial" &&
       ffprobe -v error -select_streams v:0 \\
        -show_entries stream=codec_name -of default=nw=1:nk=1 "$partial" |
        grep -qx hevc; then
        mv -- "$partial" "$output"
        printf 'Created: %s\\n' "$output"
    else
        printf 'FAILED: %s\\n' "$input" >&2
        rm -f -- "$partial"
    fi
done

find -print0 and read -d '' preserve spaces, quotes, and backslashes in filenames. ISO disc images are deliberately excluded: an ISO is a filesystem image, not necessarily a directly transcodable video stream. Extract or remux its intended title first.

The output is written to a temporary .part.mkv file. It is renamed only after FFmpeg exits successfully and ffprobe confirms an HEVC video stream. Existing completed outputs are skipped, so the job can be restarted.

What the encoder options mean

  • -c:v libx265 selects the software x265 encoder.
  • -preset medium balances encoding time and compression efficiency. A slower preset usually compresses more efficiently but is not automatically higher visual quality at the same CRF.
  • -crf 26 is a starting point, not a universal quality setting. Test short samples and adjust; lower CRF generally means higher quality and a larger file.
  • -c:a copy preserves the original audio streams. -ac 6 would only request six output channels; it cannot turn a stereo source into genuine surround information.
  • -map '0:a?' and -map '0:s?' include audio and subtitle streams when present. Review unusual files containing attachments, multiple video angles, or unsupported subtitle codecs separately.

For a supported VAAPI device, a single-file hardware test may look like:

ffmpeg -vaapi_device /dev/dri/renderD128 -i input.mkv \\
  -vf 'format=nv12,hwupload' -c:v hevc_vaapi -qp 24 \\
  -c:a copy output.mkv

That is a VAAPI HEVC encode, not x265. Device permissions, pixel formats, supported profiles, and quality options vary by GPU and driver.

Validate before removing anything

Do not add an unconditional rm after FFmpeg. A shell proceeds to the next command after many failures unless the script explicitly checks success. Compare durations with ffprobe, inspect stream mappings, and play samples from the beginning, middle, and end. Only then remove or archive originals as a separate, deliberate step.

Run a long job inside screen if desired:

screen -S convert-video
~/convert-video /media/yourvideos

Detach with Ctrl-a, then d; reattach with screen -r convert-video. Use Ctrl-c to request that the foreground FFmpeg process stop. Ctrl-z merely suspends the process and can leave the job paused.