Skip to content
Go back

HandBrake and Video Transcoding: Your Media Library Deserves Better Compression

By SumGuy 6 min read
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:

Terminal window
# Debian/Ubuntu
sudo apt install handbrake-cli
# Or via Flatpak
flatpak install flathub fr.handbrake.ghb

Basic encode, H.265 with sane defaults:

Terminal window
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)

Terminal window
# 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

Terminal window
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

Terminal window
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:

Terminal window
--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:

Terminal window
--subtitle "scan" \
--subtitle-forced

Want specific subtitle tracks? List them:

Terminal window
--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:

Terminal window
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:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Here Documents vs Here Strings in Bash
Next Post
Portainer vs Dockge: Managing Containers Without the Terminal

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts