Files
MajorWiki/04-streaming/plex/plex-4k-codec-compatibility.md
majorlinux 279c094afc wiki: add firewalld mail ports reset article + session updates
- New article: firewalld mail ports wiped after reload (IMAP + webmail outage)
- New article: Plex 4K codec compatibility (Apple TV)
- New article: mdadm RAID recovery after USB hub disconnect
- Updated yt-dlp article
- Updated all index files: SUMMARY.md, index.md, README.md, category indexes
- Article count: 41 → 42

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 16:15:02 -04:00

4.9 KiB

Plex 4K Codec Compatibility (Apple TV)

4K content on YouTube is delivered in AV1 or VP9 — neither of which the Plex app on Apple TV can direct play. This forces Plex to transcode, and most home server CPUs can't transcode 4K in real time. The fix is converting to HEVC before Plex ever sees the file.

Codec Compatibility Matrix

Codec Apple TV (Plex direct play) YouTube 4K Notes
H.264 (AVC) (max 1080p) Most compatible, but no 4K
HEVC (H.265) Best choice: 4K compatible, widely supported
VP9 Google's royalty-free codec, forces transcode
AV1 Best compression, requires modern hardware to decode

Target format: HEVC. Direct plays on Apple TV, supports 4K/HDR, and modern hardware can encode it quickly.

Why AV1 and VP9 Cause Problems

When Plex can't direct play a file it transcodes it on the server. AV1 and VP9 decoding is CPU-intensive — most home server CPUs can't keep up with 4K60 in real time. Intel Quick Sync (HD 630 era) supports VP9 hardware decode but not AV1. AV1 hardware support requires 11th-gen Intel or RTX 30-series+.

Batch Converting Existing Files

For files already in your Plex library, use this script to find all AV1/VP9 files and convert them to HEVC via VAAPI (Intel Quick Sync):

#!/bin/bash
VAAPI_DEV=/dev/dri/renderD128
PLEX_DIR="/plex/plex"
LOG="/root/av1_to_hevc.log"
TMPDIR="/tmp/av1_convert"

mkdir -p "$TMPDIR"
echo "=== AV1→HEVC batch started $(date) ===" | tee -a "$LOG"

find "$PLEX_DIR" -iname "*.mp4" -o -iname "*.mkv" | while IFS= read -r f; do
  codec=$(mediainfo --Inform='Video;%Format%' "$f" 2>/dev/null)
  [ "$codec" != "AV1" ] && [ "$codec" != "VP9" ] && continue

  echo "[$(date +%H:%M:%S)] Converting: $(basename "$f")" | tee -a "$LOG"
  tmp="${TMPDIR}/$(basename "${f%.*}").mp4"

  ffmpeg -hide_banner -loglevel error \
    -vaapi_device "$VAAPI_DEV" \
    -i "$f" \
    -vf 'format=nv12,hwupload' \
    -c:v hevc_vaapi \
    -qp 22 \
    -c:a copy \
    -movflags +faststart \
    "$tmp"

  if [ $? -eq 0 ] && [ -s "$tmp" ]; then
    mv "$tmp" "${f%.*}_hevc.mp4"
    rm -f "$f"
  else
    rm -f "$tmp"
    echo "  FAILED — original kept." | tee -a "$LOG"
  fi
done

Run in a tmux session so it survives SSH disconnect:

tmux new-session -d -s av1-convert '/root/av1_to_hevc.sh'
tail -f /root/av1_to_hevc.log

After completion, trigger a Plex library scan to pick up the renamed files.

Automating Future Downloads (yt-dlp)

Prevent the problem at the source with a post-download conversion hook.

1. Create the conversion script

Save to /usr/local/bin/yt-dlp-hevc-convert.sh:

#!/bin/bash
INPUT="$1"
VAAPI_DEV=/dev/dri/renderD128
LOG=/var/log/yt-dlp-convert.log

[ -z "$INPUT" ] && exit 0
[ ! -f "$INPUT" ] && exit 0

CODEC=$(mediainfo --Inform='Video;%Format%' "$INPUT" 2>/dev/null)
if [ "$CODEC" != "AV1" ] && [ "$CODEC" != "VP9" ]; then
  exit 0
fi

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Converting ($CODEC): $(basename "$INPUT")" >> "$LOG"
TMPOUT="${INPUT%.*}_hevc_tmp.mp4"

ffmpeg -hide_banner -loglevel error \
  -vaapi_device "$VAAPI_DEV" \
  -i "$INPUT" \
  -vf 'format=nv12,hwupload' \
  -c:v hevc_vaapi \
  -qp 22 \
  -c:a copy \
  -movflags +faststart \
  "$TMPOUT"

if [ $? -eq 0 ] && [ -s "$TMPOUT" ]; then
  mv "$TMPOUT" "${INPUT%.*}.mp4"
  [ "${INPUT%.*}.mp4" != "$INPUT" ] && rm -f "$INPUT"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] OK: $(basename "${INPUT%.*}.mp4")" >> "$LOG"
else
  rm -f "$TMPOUT"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] FAILED — original kept: $(basename "$INPUT")" >> "$LOG"
fi
chmod +x /usr/local/bin/yt-dlp-hevc-convert.sh

2. Configure yt-dlp

~/.config/yt-dlp/config:

--remote-components ejs:github
--format bestvideo+bestaudio
--merge-output-format mp4
--output /plex/plex/%(title)s.%(ext)s
--write-auto-subs
--embed-subs
--exec /usr/local/bin/yt-dlp-hevc-convert.sh {}

With this config, yt-dlp <URL> downloads the best available quality (including 4K AV1/VP9), then immediately converts any AV1 or VP9 output to HEVC before Plex indexes it.

[!note] The --format bestvideo+bestaudio selector gets true 4K from YouTube (served as AV1 or VP9). The hook converts it to HEVC. Without the hook, using bestvideo[ext=mp4] would cap downloads at 1080p since YouTube only serves H.264 up to 1080p.

Enabling Hardware Transcoding in Plex

Even with automatic conversion in place, enable hardware acceleration in Plex as a fallback for any files that slip through:

Plex Web → Settings → Transcoder → "Use hardware acceleration when available"

This requires Plex Pass. On Intel systems with Quick Sync, VP9 will hardware transcode even without pre-conversion. AV1 will still fall back to CPU on pre-Alder Lake hardware.