halfsize / HDR
HDR video that turns grey after compression
The clip looked vivid in Photos. You compressed it, and now the sky is white, the skin is grey, and everything looks like it was shot through a sock. That is not a bitrate problem, and compressing harder will not fix it.
What the iPhone actually records
From the iPhone 12 on, the default camera records HLG HDR: 10-bit pixels, BT.2020 primaries,
and a transfer curve named arib-std-b67. Android flagships often record HLG or
PQ (smpte2084, the HDR10 curve). The numbers in the file are not scene colors;
they are HDR code values that need a known display to make sense.
Two ways a compressor gets it wrong
- Keep the HDR tags. The encoder drops to 8-bit
yuv420pbut leaves the HDR metadata in place. Players that ignore HDR show the raw code values, which look flat and grey. - Drop the tags. The pixels are converted to 8-bit and the metadata is stripped, but the transfer curve is never converted. Colors shift, contrast collapses, and different players disagree about what they are looking at.
Either way the file is valid. It plays. It just looks wrong, and the person who compressed it usually blames the bitrate.
The tell: if raising the bitrate does not bring the color back, it was never a bitrate problem.
The fix is a conversion, not a checkbox
To put HDR into a normal 8-bit SDR file you have to go through linear light, compress the dynamic
range, and re-tag the result as BT.709. With FFmpeg's zscale and tonemap
filters that is one chain:
zscale=tin=arib-std-b67:min=bt2020nc:pin=bt2020:t=linear:npl=100,
format=gbrpf32le,
zscale=p=bt709,
tonemap=tonemap=hable:desat=0,
zscale=t=bt709:m=bt709:r=tv,
format=yuv420p
npl is the nominal peak luminance. HLG is relative, so 100 works; PQ is graded against
a real peak, usually 1000 nits. For a PQ source the first line becomes
tin=smpte2084:...:npl=1000.
The order matters. Tone mapping has to happen before scaling, on the source transfer curve. Scale first and you are resampling HDR code values, then guessing.
What halfsize does since 0.1.9
It reads color_transfer during the probe, applies the matching chain before the
scale filter, and tags the output bt709. --dry-run shows it:
$ npx halfsize IMG_2041.MOV --to 20mb --dry-run
IMG_2041.MOV
214 MB 38s 3840x2160 30.00 fps hevc
target 20.0 MB
plan H.264 1280x720 4.18 Mbps audio 128 kbps 2-pass
scale 3840x2160 → 1280x720
hdr HLG → SDR BT.709
The quality check needs the same treatment. Comparing an SDR output against an untouched HDR source scores the transfer curve as “compression loss” and produces a useless VMAF number, so the reference frame is tone-mapped too before SSIM and VMAF are calculated.
Check your own file
$ ffprobe -v error -select_streams v:0 \
-show_entries stream=color_transfer,color_primaries,color_space,pix_fmt \
-of default=nw=1 video.mov
color_transfer=arib-std-b67 # HLG
color_primaries=bt2020
color_space=bt2020nc
pix_fmt=yuv420p10le
If color_transfer is smpte2084, the file is PQ. If it is
bt709, it is already SDR and none of this applies. A BT.2020 matrix with an SDR transfer
is wide gamut, not HDR; re-encoding it is harmless.
“Can I keep HDR?”
Not with halfsize today. The output is SDR BT.709, which is what Discord, WhatsApp, WeChat, browsers, email clients and most video players actually display. Keeping HDR means a different pipeline and a viewer that supports it; for “send this to a group chat”, SDR is the version that looks right everywhere.
FAQ
Why does my iPhone video look grey after compressing?
The source is HLG HDR. The compressor made 8-bit pixels without converting the transfer curve, so the file either keeps HDR tags on SDR pixels or loses the metadata players need. Both look flat.
Does a higher bitrate fix it?
No. Bitrate changes detail, not color. A 50 Mbps export with the wrong curve is still grey.
Can I keep HDR in the compressed file?
Not with halfsize today. It converts to SDR BT.709, which is what most apps can display. Keeping HDR needs a separate pipeline.
How do I know if a file is HDR?
ffprobe and look at color_transfer: arib-std-b67 is HLG, smpte2084 is PQ.
Does tone mapping reduce quality?
It changes the look on purpose: HDR highlights are compressed into what an SDR screen can show. The alternative is not “full quality”, it is a grey file.