Skip to content
SumGuy's Ramblings
Go back

HandBrake and Video Transcoding: Your Media Library Deserves Better Compression

Your 40GB MKV File Is Not a Personality

Look, I get it. You downloaded the “best quality” encode, the 40GB Blu-ray remux with six audio tracks and subtitles for languages you’ve never heard of. Storage was cheap. The NAS had room. It felt responsible.

Now you’ve got a 20TB drive that’s 80% full, Jellyfin is struggling to transcode for your phone on-the-go, and you’ve started eyeing another hard drive. Stop. HandBrake exists. Let’s use it properly.

What Transcoding Actually Does (and When Not To)

Transcoding re-encodes your video from one codec to another. You’re trading some encode time for a smaller file. Done right, the quality difference is invisible to human eyes. Done wrong, you get blocky garbage.

When to transcode:

When NOT to transcode:

HandBrakeCLI: The Actually Useful Part

The GUI is fine for one-off jobs. For a library, you want the CLI:

# Debian/Ubuntu
sudo apt install handbrake-cli

# Or via Flatpak
flatpak install flathub fr.handbrake.ghb

Basic encode, H.265 with sane defaults:

HandBrakeCLI \
  -i "/media/Movies/SomeMovie.mkv" \
  -o "/media/Encoded/SomeMovie-x265.mkv" \
  --preset "H.265 MKV 1080p30" \
  --quality 22

The --quality flag sets Constant Rate Factor (CRF). Lower number = better quality, bigger file. For H.265:

Codec Trade-offs: H.264 vs H.265 vs AV1

Let’s settle this quickly:

H.264 (x264)

H.265 (x265)

AV1

My recommendation for a home library: H.265 CRF 22. You’ll store more stuff, everything plays it, and you can do it on any hardware. Switch to AV1 when your GPU supports it and you want to squeeze more.

GPU Encoding: Stop Wasting Your GPU

CPU encoding is fine. GPU encoding is faster. Much faster. Here’s how to enable it:

NVIDIA (NVENC)

# H.265 via NVENC
HandBrakeCLI \
  -i input.mkv \
  -o output.mkv \
  --encoder nvenc_h265 \
  --quality 28 \
  --encopts "spatial-aq=1:temporal-aq=1:rc-lookahead=32"

NVENC uses a fixed-quality mode slightly differently — quality 28 with NVENC is roughly equivalent to CRF 22 in x265. Adjust to taste.

Intel QuickSync

HandBrakeCLI \
  -i input.mkv \
  -o output.mkv \
  --encoder qsv_h265 \
  --quality 25

QuickSync is excellent on 11th gen+ Intel. If you’re running a Proxmox NAS with a modern Intel chip, you’re leaving performance on the table if you’re not using this.

AMD VCE

HandBrakeCLI \
  -i input.mkv \
  -o output.mkv \
  --encoder vce_h265 \
  --quality 22

Note: GPU encoding generally produces slightly larger files than CPU at the same “quality” setting because the hardware encoder is optimizing for speed, not compression efficiency. The difference is typically 10-20%. For a library of movies, that tradeoff is almost always worth it.

Batch Transcoding Script

Here’s a script that processes a directory, skips already-encoded files, and logs what it’s doing:

#!/bin/bash

INPUT_DIR="/media/Movies"
OUTPUT_DIR="/media/Encoded"
LOG_FILE="/var/log/transcode.log"
QUALITY=22
ENCODER="nvenc_h265"  # Change to x265 for CPU

mkdir -p "$OUTPUT_DIR"

find "$INPUT_DIR" -type f \( -name "*.mkv" -o -name "*.mp4" -o -name "*.avi" \) | while read -r input_file; do
    filename=$(basename "$input_file")
    output_file="$OUTPUT_DIR/${filename%.*}-encoded.mkv"

    # Skip if already encoded
    if [[ -f "$output_file" ]]; then
        echo "[SKIP] $filename already encoded" | tee -a "$LOG_FILE"
        continue
    fi

    echo "[START] $(date): $filename" | tee -a "$LOG_FILE"

    HandBrakeCLI \
        -i "$input_file" \
        -o "$output_file" \
        --encoder "$ENCODER" \
        --quality "$QUALITY" \
        --audio "1,2" \
        --aencoder copy \
        --subtitle "scan" \
        --subtitle-forced \
        2>> "$LOG_FILE"

    if [[ $? -eq 0 ]]; then
        echo "[DONE] $(date): $filename" | tee -a "$LOG_FILE"
    else
        echo "[FAIL] $(date): $filename" | tee -a "$LOG_FILE"
        rm -f "$output_file"  # Don't leave partial files
    fi
done

Run this in a tmux session overnight. Come back to a smaller library.

Audio and Subtitle Handling

Don’t re-encode audio unless you have to. Copy it:

--aencoder copy      # Copy all audio tracks as-is
--audio "1,2"        # Only keep first two audio tracks (ditch the commentary tracks nobody watches)

For subtitles, the scan mode is smart — it keeps forced subtitles (the ones that show when characters speak a foreign language) without burning in full subtitles:

--subtitle "scan" \
--subtitle-forced

Want specific subtitle tracks? List them:

--subtitle "1,3" \
--srt-file "/path/to/extra.srt"

HandBrake in Docker

Running on a server without a desktop? Docker keeps it clean:

# Dockerfile
FROM jlesage/handbrake:latest

Or just run the official image for CLI use:

docker run --rm \
  -v /media:/media \
  --gpus all \  # For NVENC, needs nvidia-container-toolkit
  jlesage/handbrake:latest \
  HandBrakeCLI -i /media/input.mkv -o /media/output.mkv --encoder nvenc_h265 --quality 28

For a persistent watch folder setup (auto-transcode anything dropped in a folder), jlesage/handbrake has that built in via the AUTOMATED_CONVERSION environment variables. Genuinely useful.

Jellyfin/Plex Integration

After encoding, update your media library path. The big win: if you’ve encoded everything to H.265 in a container your clients support, Jellyfin/Plex can direct play instead of transcoding on the fly. Direct play means:

Check Jellyfin’s playback logs to confirm direct play is happening. If you see “Transcode” in the logs, either the codec isn’t supported by the client or the container format is wrong — MKV and MP4 both work broadly; MKV is more flexible for multiple tracks.

The rule: transcode your library once, good, offline. Never let your server transcode live if you can avoid it. Your server (and your electricity bill) will thank you.


Share this post on:

Previous Post
Open Source Security: Scanning Your Dependencies Before They Scan You
Next Post
Portainer vs Dockge: Managing Containers Without the Terminal