Re-encode cached videos to HEVC when it saves at least 15 percent and serve them only to devices that can decode HEVC

This commit is contained in:
Jonathan Sykes
2026-09-13 21:09:15 +08:00
parent 91dab289c3
commit 8b0060d42a
7 changed files with 159 additions and 55 deletions

View File

@@ -42,10 +42,19 @@ JSON shapes mirror the Tauri Rust bridge exactly — don't change one side alone
(15 min · 2^n, max 24 h) and saves fall back to the legacy per-request path.
- `gen` is in the filename and URL (`/api/media/<id>?g=<gen>`): a URL never serves
different bytes. A replaced gen is kept 30 min so in-flight Range playback survives.
- **Compression lane**: niced x264 `-preset slow -crf 28 -fpsmax 30`, audio copied,
kept only if ≥15% smaller. Measured: YouTube's 720p avc1 is already ~0.6–0.7 Mbps, so
CRF 24 made files *bigger*; CRF 28 ≈ −20% video at SSIM 0.994. Lyric videos usually
keep the original. HEVC/AV1/10-bit are smaller but don't play in iOS `<video>`.
- **Compression lane** (rev `OPT_REV` = 2): niced x265 8-bit `-preset medium -crf 28`,
`hvc1` tag, anime-style tuning (`X265_TUNING`), `-fpsmax 30`, audio copied — kept only
if ≥15% smaller, and the copy's `vcodec` becomes `hevc`. Benchmarked 2026-09-13 (VMAF,
codec-test page in Windows Docs): x264 can NOT beat YouTube's own H.264 (needs more
bitrate at equal quality); x265 cuts live worship video ~35–44% at ~2× realtime on the
homelab's 2 threads; lyric videos (~47 kbps video, mostly audio) keep the original.
Bumping `OPT_REV` re-queues every cached copy once at boot.
- **HEVC gating**: an HEVC copy is served only to clients that send `?hevc=1` on
`/api/streams` and `/api/download` (app.js `hevcCapable()` = canPlayType hvc1, minus a
per-device `ytpNoHevc` flag set when an HEVC copy fails to play). Everyone else gets
the pre-cache behaviour (YouTube proxy / legacy H.264 save). Verified: iPhone XR, Galaxy
S10, Windows Chrome play HEVC; AV1 fails on the XR (no decoder), so it isn't used.
The validation gate requires `hvc1` (Safari refuses `hev1`) and 8-bit 4:2:0.
- **Budget**: `MEDIA_CACHE_MAX_BYTES` (10 GiB) LRU by `last_access` (anything played in
the last 10 min is protected) + `MEDIA_MIN_FREE_BYTES` (5 GiB) disk guard → skip, stream.
- **Broken**: `POST /api/media/:id/redownload` (now-playing ⚠ Broken button). When a