audit: rick-and-morty color/HDR diagnosis
This commit is contained in:
parent
efdd43d1a8
commit
2f8c02a2d3
1 changed files with 482 additions and 0 deletions
482
docs/21-rick-and-morty-color-audit.md
Normal file
482
docs/21-rick-and-morty-color-audit.md
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
# 21 — Rick and Morty Color / HDR Audit (Read-Only)
|
||||
|
||||
> Status: **read-only audit**, executed 2026-05-08 against
|
||||
> `https://arrflix.s8n.ru` (Jellyfin 10.10.3 on nullstone). Scope:
|
||||
> diagnose why **Rick and Morty looks "kind of gray / washed-out"**
|
||||
> while other titles render normally. **No fixes applied. No state
|
||||
> mutated. No transcode triggered.**
|
||||
>
|
||||
> Inputs: `ffprobe` via `docker exec jellyfin /usr/lib/jellyfin-ffmpeg/ffprobe`
|
||||
> against on-disk media; Jellyfin REST `/Items/{id}/PlaybackInfo`,
|
||||
> `/System/Configuration/encoding`, `/Branding/Configuration` (auth
|
||||
> `X-Emby-Token: ${JELLYFIN_API_TOKEN}`); contrast probe against
|
||||
> *The Mandalorian* as a known-good SDR title; review of `CustomCss`
|
||||
> against the inventory in doc 14 §1b.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
**Confirmed root cause:** the Rick and Morty release on disk is an
|
||||
**HDR10 4K HEVC Main 10 (PQ / BT.2020) "AI Upscale"** of an originally
|
||||
SDR animated show. Jellyfin classifies it as `VideoRange=HDR`
|
||||
`VideoRangeType=HDR10` and forces the browser onto the **transcode
|
||||
path** (`TranscodeReasons=ContainerNotSupported, AudioCodecNotSupported,
|
||||
SubtitleCodecNotSupported` — every browser session triggers this). The
|
||||
encoding config has **`EnableTonemapping=false` and
|
||||
`HardwareAccelerationType=none`**, so ffmpeg software-decodes the
|
||||
HDR10 source, then h264-encodes **without applying a tonemap**, then
|
||||
the browser interprets the resulting BT.2020 PQ pixel data as plain
|
||||
BT.709 SDR. That mis-interpretation is the textbook signature of the
|
||||
washed-out grey look.
|
||||
|
||||
**One-line remediation (lowest blast radius):** in
|
||||
`/System/Configuration/encoding`, set `EnableTonemapping=true` (the
|
||||
algorithm `bt2390` is already correctly selected) — this enables CPU
|
||||
tonemap on the existing software pipeline; CSS, hardware, and source
|
||||
files do not need to change.
|
||||
|
||||
CSS / theme is **ruled out** as a cause — `CustomCss` contains zero
|
||||
`grayscale(`, zero `saturate(`, zero `hue-rotate(` filters.
|
||||
|
||||
---
|
||||
|
||||
## 2. ffprobe table — Rick and Morty (Season 01)
|
||||
|
||||
All probes via `docker exec jellyfin /usr/lib/jellyfin-ffmpeg/ffprobe -v error -select_streams v:0 …`.
|
||||
|
||||
| File | Codec | Profile | Pix fmt | color_space | color_transfer | color_primaries | range | W×H | Bitrate | Size | HDR side-data |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| S01E01 — Pilot | hevc | Main 10 | yuv420p10le | bt2020nc | **smpte2084** (PQ) | bt2020 | pc | 3840×2160 | 8.13 Mbit/s | 1.34 GB | **none** (no MasteringDisplay / CLL block) |
|
||||
| S01E05 — Meeseeks and Destroy | hevc | Main 10 | yuv420p10le | bt2020nc | **smpte2084** | bt2020 | pc | 3840×2160 | 7.97 Mbit/s | 1.26 GB | not present |
|
||||
| S01E08 — Rixty Minutes | hevc | Main 10 | yuv420p10le | bt2020nc | **smpte2084** | (BT.2020) | (pc) | 3840×2160 | n/a | 1.34 GB | not present |
|
||||
| S01E11 — Ricksy Business | hevc | Main 10 | yuv420p10le | bt2020nc | **smpte2084** | bt2020 | pc | 3840×2160 | 8.86 Mbit/s | 1.49 GB | not present |
|
||||
|
||||
**Reading:**
|
||||
|
||||
- `color_transfer=smpte2084` (a.k.a. ST 2084 / PQ) is the **HDR10
|
||||
transfer function**. All R&M S01 episodes ship with HDR10 tagging.
|
||||
- `color_primaries=bt2020` + `color_space=bt2020nc` are the BT.2020
|
||||
wide-gamut primaries (the HDR colour space).
|
||||
- `pix_fmt=yuv420p10le` = 10-bit-per-component, 4:2:0 chroma sub-
|
||||
sampling. Required for HDR10 content.
|
||||
- `color_range=pc` = full-range (0–1023 for 10-bit) rather than the
|
||||
TV-range (64–940) usually expected. **This is unusual** — most HDR10
|
||||
Blu-ray / streaming sources are TV-range. PC-range mis-interpreted
|
||||
as TV-range is itself a contrast/saturation hit, layered on top of
|
||||
the HDR-as-SDR hit.
|
||||
- **No HDR side-data** (`MasteringDisplayMetadata`,
|
||||
`ContentLightLevelMetadata`) is present in any episode — the source
|
||||
declares HDR10 but ships without the static-metadata blocks that a
|
||||
proper HDR display or tonemapper would consume. This is a
|
||||
fingerprint of a **fake HDR10** AI upscale (the file's own embedded
|
||||
title is `"Rick and Morty - S01E01 - Pilot - 2160p HDR Ai Upscale -Mesc"`).
|
||||
- 4K x 24 fps x ~8 Mbit/s × 1320 s = file size matches container
|
||||
declaration, no surprises in muxing.
|
||||
- The poster art / show landing page itself is rendered by the SPA
|
||||
from JF's image cache (PNG / JPEG, sRGB) — those are not affected by
|
||||
HDR. Only the **video element** is washed-out.
|
||||
|
||||
### 2a. Comparison vs. The Mandalorian (known-good SDR)
|
||||
|
||||
| File | Codec | Profile | Pix fmt | color_space | color_transfer | W×H | Bitrate |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Mandalorian S01E01 | hevc | Main 10 | yuv420p10le | **bt709** | **bt709** | 1920×804 | 6.69 Mbit/s |
|
||||
| Mandalorian S02E01 | hevc | Main 10 | yuv420p10le | **bt709** | **bt709** | (1920×…) | n/a |
|
||||
| Mandalorian S03E01 | hevc | Main 10 | yuv420p10le | **bt709** | **bt709** | 1920×804 | 6.72 Mbit/s |
|
||||
|
||||
**Reading:** Mandalorian is **plain SDR BT.709** (the same colour space
|
||||
the browser's `<video>` defaults to assume). 10-bit pixels here are
|
||||
fine because the *transfer* is BT.709 SDR, not PQ — the browser /
|
||||
ffmpeg pipeline sees this and renders it correctly. This is the
|
||||
control sample that proves the difference is *content-side*, not
|
||||
config-side.
|
||||
|
||||
---
|
||||
|
||||
## 3. Jellyfin encoding config — relevant fields
|
||||
|
||||
Source: `GET /System/Configuration/encoding`.
|
||||
|
||||
| Field | Value | Comment |
|
||||
|---|---|---|
|
||||
| `HardwareAccelerationType` | `"none"` | **GPU is dead** (host has no nvidia driver — see doc 13 finding 02). Every transcode is software ffmpeg. |
|
||||
| `EnableHardwareEncoding` | `true` | No-op while `HardwareAccelerationType=none`. |
|
||||
| `EnableTonemapping` | **`false`** | **THE BUG.** Software-tonemap is disabled. With HDR source + `=none` HW + tonemap off, the output is HDR pixels with no SDR conversion. |
|
||||
| `EnableVppTonemapping` | `false` | Intel-VPP path, not relevant for CPU. |
|
||||
| `EnableVideoToolboxTonemapping` | `false` | macOS path, not relevant. |
|
||||
| `TonemappingAlgorithm` | `"bt2390"` | **Good choice** when enabled — the BT.2390 EETF is the modern recommendation. (`hable` is the legacy fallback; `mobius` and `reinhard` are alternatives.) |
|
||||
| `TonemappingMode` | `"auto"` | Fine. |
|
||||
| `TonemappingRange` | `"auto"` | Fine. |
|
||||
| `TonemappingDesat` | `0` | Default. |
|
||||
| `TonemappingPeak` | `100` | Target SDR peak nits — default. |
|
||||
| `TonemappingParam` | `0` | Algorithm-specific; 0 = default. |
|
||||
| `EnableDecodingColorDepth10Hevc` | `true` | 10-bit HEVC decode permitted. |
|
||||
| `H264Crf` | `23` | h264 quality target for transcode output (default for JF). |
|
||||
| `H265Crf` | `28` | h265 quality target (unused — `AllowHevcEncoding=false`). |
|
||||
| `AllowHevcEncoding` | `false` | Cannot transcode-out as HEVC (forces h264 output). |
|
||||
| `AllowAv1Encoding` | `false` | Cannot transcode-out as AV1. |
|
||||
| `EnableThrottling` | `false` | Per doc 13 finding 03 — separate issue. |
|
||||
| `EnableSegmentDeletion` | `false` | Per doc 13 finding 05 — separate issue. |
|
||||
| `MaxMuxingQueueSize` | `2048` | Per doc 13 — separate issue. |
|
||||
| `EncoderAppPathDisplay` | `/usr/lib/jellyfin-ffmpeg/ffmpeg` | Bundled jellyfin-ffmpeg, not host. |
|
||||
| `VaapiDevice` | `/dev/dri/renderD128` | Empty on host (no Intel iGPU on AMD Ryzen). |
|
||||
| `EncodingThreadCount` | `-1` | Auto = all cores. |
|
||||
|
||||
**Net:** the *one* knob standing between "washed-out grey" and
|
||||
"correctly tonemapped SDR" is `EnableTonemapping`. The algorithm is
|
||||
already set correctly (`bt2390`). Flipping the bool to `true` is a
|
||||
single POST-able field-edit and applies to every future transcode.
|
||||
|
||||
### 3a. Live PlaybackInfo for R&M S01E01 (browser DeviceProfile)
|
||||
|
||||
Simulated browser PlaybackInfo (DeviceProfile: Chrome, h264 / aac /
|
||||
mp3 / ac3 / eac3, hls):
|
||||
|
||||
```
|
||||
SupportsDirectPlay: false
|
||||
SupportsDirectStream: false
|
||||
SupportsTranscoding: true
|
||||
TranscodingSubProtocol: hls
|
||||
TranscodingUrl:
|
||||
/videos/<id>/master.m3u8
|
||||
?VideoCodec=h264
|
||||
&AudioCodec=aac,mp3,ac3,eac3
|
||||
&VideoBitrate=139616000
|
||||
&SegmentContainer=ts
|
||||
&hevc-level=150
|
||||
&hevc-videobitdepth=10
|
||||
&hevc-profile=main10
|
||||
&TranscodeReasons=
|
||||
ContainerNotSupported,
|
||||
AudioCodecNotSupported,
|
||||
SubtitleCodecNotSupported
|
||||
```
|
||||
|
||||
**Reading:** every browser session for R&M is forced into transcode by
|
||||
three independent reasons (container `mkv`, audio `truehd` / `ac3`,
|
||||
subtitle `pgs` / `ass` — confirmed by MediaInfo). It's not just an HDR
|
||||
issue — the file *cannot* direct-play in any browser, so the transcode
|
||||
path is mandatory, and inside that path tonemap is currently off.
|
||||
|
||||
For comparison, an SDR Mandalorian episode would still hit the
|
||||
transcode path for the same container/audio reasons, but the
|
||||
tonemap-off flag wouldn't matter because the source is already BT.709.
|
||||
|
||||
---
|
||||
|
||||
## 4. Theme / CSS rule-out check
|
||||
|
||||
Inspected `/Branding/Configuration → CustomCss` (25 225 chars, full
|
||||
inventory in doc 14 §1b). Searched the live string for any
|
||||
filter / saturation / hue-rotate / opacity rule that could desaturate
|
||||
the video element or its container.
|
||||
|
||||
| Filter pattern | Matches in CustomCss | Verdict |
|
||||
|---|---|---|
|
||||
| `grayscale(` | **0** | ✓ |
|
||||
| `saturate(` | **0** | ✓ |
|
||||
| `hue-rotate(` | **0** | ✓ |
|
||||
| `sepia(` | **0** | ✓ |
|
||||
| `brightness(` | **0** | ✓ |
|
||||
| `contrast(` | **0** | ✓ |
|
||||
| `invert(` | **0** | ✓ |
|
||||
| `mix-blend-mode` | **0** | ✓ |
|
||||
| `filter:` | **0** | ✓ |
|
||||
| `backdrop-filter:` | **0** | ✓ |
|
||||
| `opacity:` (on `.itemBackdrop` / `video` / `.osdContainer`) | **0** | ✓ |
|
||||
|
||||
Also checked the doc 14 §7 detail-page backdrop rules just landed
|
||||
(`linear-gradient(90deg, rgba(0,0,0,0.95) 0%, …)`) — that gradient is
|
||||
applied to `.layout-desktop .backgroundContainer.withBackdrop`, NOT to
|
||||
the `<video>` element. It tints the *backdrop poster behind the
|
||||
detail-page header*, not playback. **Not the cause.**
|
||||
|
||||
`web-overrides/index.html` (the bind-mounted critical-path style): no
|
||||
`filter:`, no `mix-blend-mode`, no animation that would alter video.
|
||||
`ARRFLIX-SHIM` JavaScript only touches `document.title`, favicon, and
|
||||
`mypreferencesmenu` drawer entries — does not touch playback DOM.
|
||||
|
||||
**Theme / CSS rule-out: PASS.** The greyness is in the pixel data
|
||||
delivered to the browser, not in any post-render CSS effect.
|
||||
|
||||
---
|
||||
|
||||
## 5. Source-file integrity rule-outs
|
||||
|
||||
Already visible in §2, but stated explicitly so each candidate root
|
||||
cause is closed:
|
||||
|
||||
| Hypothesis | Evidence | Verdict |
|
||||
|---|---|---|
|
||||
| (a) HDR file + CPU tone-map | All R&M S01 = HDR10 (`smpte2084`/`bt2020`). Encoding config `EnableTonemapping=false`, `HardwareAccelerationType=none`. | **CONFIRMED** root cause. |
|
||||
| (b) CSS filter on theme | §4 shows zero filter/saturation rules. | RULED OUT. |
|
||||
| (c) Direct-play tag mismatch | PlaybackInfo §3a shows `SupportsDirectPlay=false` — browser is on transcode path, no chance of DP-tag confusion. | RULED OUT. |
|
||||
| (d) Source is genuinely SDR but graded flat (wrong tags) | ffprobe reports HDR10 tags consistently across 4 episodes, and Jellyfin agrees (`VideoRangeType=HDR10`). Title-string `"2160p HDR Ai Upscale"` confirms intent. | RULED OUT — the source IS HDR10, just badly so. |
|
||||
| (e) Container / bit-depth / browser HW-decode bit-crush | Browser never receives the 10-bit HEVC because transcode is mandatory; output is 8-bit h264. So no client-side bit-depth issue is possible. | RULED OUT. |
|
||||
| (f) Missing Mastering Display / CLL metadata makes tonemap target unknown | True — files have no static HDR metadata. Once tonemap is enabled, ffmpeg will fall back to defaults (peak 1000 nits, etc.) which is fine for cartoon AI-upscale content; better than no tonemap. | NOT a blocker for the fix. |
|
||||
| (g) `color_range=pc` (full-range) | Full-range PC pixels reinterpreted as TV-range = an additional contrast bump. Tonemap filter handles range conversion. | Subsumed by (a) — same fix. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Concrete remediation list (ranked: effort vs blast-radius)
|
||||
|
||||
### #1 — Enable software tonemap (recommended)
|
||||
|
||||
**Action:** flip a single bool in encoding config.
|
||||
|
||||
```
|
||||
PUT /System/Configuration/encoding
|
||||
EnableTonemapping = true
|
||||
(TonemappingAlgorithm already = "bt2390" — leave as-is)
|
||||
(TonemappingPeak already = 100 — leave as-is)
|
||||
(TonemappingMode already = "auto" — leave as-is)
|
||||
(TonemappingRange already = "auto" — leave as-is)
|
||||
(TonemappingDesat already = 0 — leave as-is)
|
||||
```
|
||||
|
||||
(Or via UI: *Dashboard → Playback → Transcoding → "Enable
|
||||
tone-mapping"*.)
|
||||
|
||||
**Effect:** every future HDR-source transcode applies BT.2390 EETF +
|
||||
gamut conversion (BT.2020 → BT.709) before h264 encoding. Output looks
|
||||
right in any SDR browser.
|
||||
|
||||
**Cost:** zero seek time, no restart needed.
|
||||
|
||||
**Blast radius:** **low.** Only HDR sources (currently: Rick and
|
||||
Morty S01) are affected. SDR sources (Mandalorian etc.) already have
|
||||
BT.709 tags so the tonemap filter is a no-op for them.
|
||||
|
||||
**Caveat:** software tonemap on a 4K HEVC source on the existing
|
||||
host load (doc 13 finding 01: load 11.4, swap 6.8 GiB) will add
|
||||
~1.5–2× extra CPU per stream compared to a tonemap-off transcode.
|
||||
Pair this with **doc 13 finding 03 (`EnableThrottling=true`)** so a
|
||||
client-cancelled stream stops burning CPU; otherwise a stalled R&M
|
||||
playback will eat a core for 12 minutes (`SegmentKeepSeconds=720`).
|
||||
|
||||
**Risk of "looks worse than expected":** AI-upscale R&M has no real
|
||||
HDR — the wide-gamut tonemap will give a result that is more saturated
|
||||
than the original Adult Swim broadcast (cartoon flat colours pushed
|
||||
through BT.2020 round-trip), but visibly correct relative to current
|
||||
washed-out grey. If the operator wants the original cartoon look,
|
||||
remediation #3 below.
|
||||
|
||||
### #2 — Pair tonemap-on with throttling-on (doc 13 finding 03)
|
||||
|
||||
**Action:** when applying #1, also set:
|
||||
|
||||
```
|
||||
EnableThrottling = true
|
||||
EnableSegmentDeletion = true
|
||||
```
|
||||
|
||||
**Effect:** caps wasted ffmpeg CPU after a client disconnects — already
|
||||
recommended in doc 13 audit, doubly important once we add tonemap
|
||||
overhead.
|
||||
|
||||
**Cost:** zero additional. Same UI page as #1.
|
||||
|
||||
### #3 — Replace R&M with a properly-graded SDR release (highest fidelity, highest effort)
|
||||
|
||||
**Action:** swap the `Rick.and.Morty.S01...2160p.HDR.Ai.Upscale-Mesc`
|
||||
files for a native SDR encode (e.g., the original Adult Swim
|
||||
1080p / WEB-DL releases or the 2160p SDR remasters where they exist).
|
||||
|
||||
**Effect:** zero tonemap cost (source is already BT.709), faster
|
||||
transcodes, files shrink ~3-4× (8 Mbit/s 4K HDR → ~2 Mbit/s 1080p
|
||||
SDR for a 22-min cartoon is plenty), consistent look with rest of
|
||||
library.
|
||||
|
||||
**Cost:** medium — re-acquisition + re-import + re-scan + 90 GB disk
|
||||
freed on `/home` which is currently 90% full (doc 13 finding 01).
|
||||
|
||||
**Blast radius:** medium. Watched-state and metadata stay (Sonarr
|
||||
will re-match by `(2013)` + episode index), but each episode item ID
|
||||
in JF will change → existing playback positions on R&M are lost.
|
||||
|
||||
### #4 — Pre-transcode R&M S01 to SDR offline (middle-ground)
|
||||
|
||||
**Action:** run `ffmpeg` once (outside Jellyfin) with the same
|
||||
tonemap pipeline, write SDR-tagged HEVC files alongside, swap them in.
|
||||
|
||||
```sh
|
||||
# Per episode (CPU intensive, ~1 hr per 22-min episode on this host):
|
||||
ffmpeg -i in.mkv \
|
||||
-map 0:v:0 -map 0:a -map 0:s? \
|
||||
-vf "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=bt2390:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p10le" \
|
||||
-c:v libx265 -preset slow -crf 22 -profile:v main10 \
|
||||
-c:a copy -c:s copy out.mkv
|
||||
```
|
||||
|
||||
**Effect:** Jellyfin no longer needs to tonemap on every play —
|
||||
files are SDR-tagged at rest. CPU at playback drops to a normal
|
||||
HEVC-software-decode-then-h264-software-encode (still no GPU but
|
||||
no extra tonemap stage).
|
||||
|
||||
**Cost:** ~11 hours wall-clock on the existing 12-core box for
|
||||
S01's 11 episodes (CPU-only HEVC encode); +20 GB during transcode,
|
||||
files end ~30% smaller than HDR originals.
|
||||
|
||||
**Blast radius:** medium-low. Rewrites only R&M — other library
|
||||
entries untouched. Item IDs change (same as #3).
|
||||
|
||||
### #5 — Wait for GPU restoration, then enable VPP / NVENC tonemap
|
||||
|
||||
**Action:** once nvidia driver is back on the host (doc 13
|
||||
finding 02), set:
|
||||
|
||||
```
|
||||
HardwareAccelerationType = nvenc
|
||||
EnableTonemapping = true
|
||||
EnableVppTonemapping = true (if Intel — N/A on Ryzen)
|
||||
HardwareDecodingCodecs = [hevc, h264, vc1] (add hevc)
|
||||
```
|
||||
|
||||
**Effect:** GPU does the HEVC decode + tonemap + h264 encode. No CPU
|
||||
load, real-time on 4K. This is the long-term right answer.
|
||||
|
||||
**Cost:** L (host driver work). Already on doc 13 fix-list.
|
||||
|
||||
**Blast radius:** large but already planned. Until GPU is back, do
|
||||
remediation #1.
|
||||
|
||||
### Recommended order
|
||||
|
||||
1. **Apply #1 + #2 today** (single Playback-settings page edit). Cost
|
||||
~30 s of ops time, immediate visual fix on R&M, no media churn.
|
||||
2. Re-test R&M playback (see §7).
|
||||
3. If the tonemapped result still feels "wrong" because R&M is a
|
||||
cartoon and the AI-upscale's HDR is a fiction, go to **#4 or #3**
|
||||
for the long-term cure.
|
||||
4. Park #5 behind the GPU restoration backlog.
|
||||
|
||||
---
|
||||
|
||||
## 7. Test plan to verify after fix
|
||||
|
||||
### 7a. Pre-fix baseline (capture now, before flipping the bool)
|
||||
|
||||
1. Open `https://arrflix.s8n.ru/web/#/details?id=324f75b84f394a5d9b0749c0679f23b9`
|
||||
in Chrome/Firefox on onyx.
|
||||
2. Hit Play. Pause at ~30 s in.
|
||||
3. Take a screenshot (full-window). File:
|
||||
`evidence/21-pre-fix-rm-s01e01-30s.png`.
|
||||
4. Note the visible characteristics: Rick's lab-coat (should be pure
|
||||
white but currently looks pale-grey), background green of the
|
||||
garage, skin tones.
|
||||
|
||||
### 7b. Apply remediation #1 + #2
|
||||
|
||||
UI path: *Dashboard → Playback → Transcoding*:
|
||||
|
||||
- Enable "Tone-mapping"
|
||||
- Enable "Throttle transcodes"
|
||||
- Enable "Delete transcode segments"
|
||||
|
||||
(Or POST `/System/Configuration/encoding` directly with the three
|
||||
bools flipped.)
|
||||
|
||||
No restart needed — Jellyfin re-reads `encoding.xml` per request.
|
||||
|
||||
### 7c. Post-fix verification
|
||||
|
||||
1. **New playback session** — close and reopen the browser tab so the
|
||||
SPA requests a fresh `PlaybackInfo` and a fresh `master.m3u8`
|
||||
(existing in-flight transcode is locked to the pre-fix ffmpeg
|
||||
command line). Easiest: hard-reload (`Ctrl-Shift-R`) and re-click
|
||||
Play.
|
||||
2. Pause at the same ~30 s mark.
|
||||
3. Screenshot to `evidence/21-post-fix-rm-s01e01-30s.png`.
|
||||
4. **Side-by-side compare** the two images. Expectations:
|
||||
- Whites are noticeably whiter (lab coat, ship hull).
|
||||
- Saturation is higher (garage greens, sky blues, characters).
|
||||
- Black-level remains similar (or slightly deeper).
|
||||
- Skin tones look natural rather than greenish-grey.
|
||||
|
||||
### 7d. Server-side sanity checks (5 min after first post-fix play)
|
||||
|
||||
```sh
|
||||
# Confirm tonemap is in the actual ffmpeg command line for this stream
|
||||
ssh user@192.168.0.100 \
|
||||
"docker exec jellyfin ps -ef | grep ffmpeg | grep -E 'tonemap|zscale' | head"
|
||||
|
||||
# Expected: a process line containing `zscale=...:t=linear:...,tonemap=bt2390,...`
|
||||
# If the line lacks `tonemap`, the encoding.xml change didn't apply or
|
||||
# JF has a cached transcode session — bounce the container.
|
||||
|
||||
# Confirm HDR-aware filter graph fed only by HDR sources (Mandalorian
|
||||
# should NOT have tonemap in its ffmpeg cmdline)
|
||||
ssh user@192.168.0.100 \
|
||||
"docker exec jellyfin tail -200 /config/log/log_*.log | grep -E 'tonemap|smpte2084'"
|
||||
```
|
||||
|
||||
### 7e. Negative test (other libraries unaffected)
|
||||
|
||||
Play one episode of:
|
||||
|
||||
- Mandalorian (SDR BT.709) — should look identical pre/post.
|
||||
- Futurama / American Dad / Obi-Wan — same expectation (probe these
|
||||
if you want to be thorough; they're outside this audit's scope).
|
||||
|
||||
If any of these now look *over*-saturated or *under*-saturated post-fix,
|
||||
the tonemap is leaking onto SDR sources — open a bug, set
|
||||
`TonemappingMode` from `auto` to a stricter mode.
|
||||
|
||||
### 7f. Performance check (CPU is the operative resource)
|
||||
|
||||
While the post-fix R&M episode is playing:
|
||||
|
||||
```sh
|
||||
ssh user@192.168.0.100 "uptime && top -bn1 -p \$(pgrep -f 'ffmpeg.*Rick.and.Morty' | head -1) | tail -5"
|
||||
```
|
||||
|
||||
- Expect ffmpeg to consume ~600–900 % CPU (6–9 cores) on this host
|
||||
for a 4K HEVC→h264 + tonemap pipeline.
|
||||
- If load average climbs past 16 sustained or swap usage grows past
|
||||
baseline 6.8 GiB, escalate doc 13 finding 01 — pair with
|
||||
remediation #4 (pre-transcode the season) sooner rather than later.
|
||||
|
||||
### 7g. Long-term verification
|
||||
|
||||
A week after the fix, check:
|
||||
|
||||
```sh
|
||||
# Number of 499 client-cancel events on jellyfin@docker
|
||||
docker logs traefik --since 168h 2>&1 | grep '"jellyfin@docker"' | grep ' 499 ' | wc -l
|
||||
```
|
||||
|
||||
Should be ≤ pre-fix baseline (currently 2 / hour, doc 13 finding 03).
|
||||
If it climbs after enabling tonemap (because the tonemap stage
|
||||
slowed transcodes enough to let the client time out), that's the
|
||||
trigger to invest in remediation #4 or #5.
|
||||
|
||||
---
|
||||
|
||||
## 8. What was NOT touched during this audit
|
||||
|
||||
- No POST/PUT to `/System/Configuration/encoding`.
|
||||
- No POST to `/System/Configuration/branding`.
|
||||
- No `docker exec jellyfin` writes (read-only `ls`, `cat`, `ffprobe`).
|
||||
- No `docker compose` action, no container restart.
|
||||
- No file modification on `/home/user/media/`.
|
||||
- No transcode triggered (PlaybackInfo simulation only — that endpoint
|
||||
decides codec paths but does not start ffmpeg).
|
||||
|
||||
---
|
||||
|
||||
## 9. Sign-off
|
||||
|
||||
- **Auditor:** s8n (audit pass, 2026-05-08)
|
||||
- **Live config at audit time:** Jellyfin 10.10.3,
|
||||
`EnableTonemapping=false`, `HardwareAccelerationType=none`,
|
||||
`TonemappingAlgorithm=bt2390`. CSS = Cineplex v1.0.6 + ARRFLIX
|
||||
brand layer (no greyscale filters).
|
||||
- **Confirmed root cause:** HDR10 source (R&M S01) + CPU-only
|
||||
pipeline + tonemap disabled = HDR pixels delivered as SDR =
|
||||
washed-out grey.
|
||||
- **Recommended fix:** flip `EnableTonemapping=true` (one
|
||||
Playback-settings checkbox) AND `EnableThrottling=true` +
|
||||
`EnableSegmentDeletion=true` (pair-finding from doc 13).
|
||||
- **Next audit due:** **2026-08-08** alongside doc 13's quarterly
|
||||
rotation, or sooner if a new HDR source lands in another library.
|
||||
Loading…
Reference in a new issue